gohugoio/hugo · critical
errNoOp
errNoOp
Error message
this operation is not supported
What it means
Hugo's `hugofs.NoOpFs` is a stub afero.Fs used where a real filesystem must not be touched (e.g. read-only or placeholder mounts). Read-style calls return os.ErrNotExist, but mutating operations like Create, Rename, Chmod, Chtimes and Chown panic with errNoOp because reaching them indicates a code path is trying to write through a filesystem that was explicitly wired to do nothing.
Source
Thrown at hugofs/noop_fs.go:25
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugofs
import (
"errors"
"os"
"time"
"github.com/spf13/afero"
)
var (
errNoOp = errors.New("this operation is not supported")
_ afero.Fs = (*noOpFs)(nil)
// NoOpFs provides a no-op filesystem that implements the afero.Fs
// interface.
NoOpFs = &noOpFs{}
)
type noOpFs struct{}
func (fs noOpFs) Create(name string) (afero.File, error) {
panic(errNoOp)
}
func (fs noOpFs) Mkdir(name string, perm os.FileMode) error {
return nil
}
func (fs noOpFs) MkdirAll(path string, perm os.FileMode) error {View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Find the panicking call site in the stack trace and identify which component received NoOpFs instead of a real filesystem.
- Wire the correct writable Fs (e.g. the OS or destination filesystem) into that component instead of NoOpFs.
- If the operation is genuinely optional in a no-op context, guard the call so mutating operations are skipped for NoOpFs.
Example fix
// before
fs := hugofs.NoOpFs
f, _ := fs.Create("out.txt") // panics: this operation is not supported
// after
fs := afero.NewOsFs()
f, err := fs.Create("out.txt") Defensive patterns
Strategy: type-guard
Validate before calling
// Don't pass a noop/stub Fs where real IO is needed
if _, ok := fs.(interface{ Name() string }); ok && fs.Name() == "noopFs" {
return fmt.Errorf("filesystem is a no-op stub; configure a real Fs")
} Type guard
func isNoopFs(fs afero.Fs) bool {
return fs == nil || fs.Name() == "noopFs"
} Try / catch
if err := doFsOp(fs); err != nil {
if strings.Contains(err.Error(), "operation is not supported") {
return fmt.Errorf("fs %q does not support this operation: %w", fs.Name(), err)
}
return err
} Prevention
- Only call read/write operations on filesystems you constructed with real backing (afero.NewOsFs, hugofs.Os)
- Treat hugofs.NoOpFs as a sentinel for disabled features (e.g. publishing disabled) and branch before doing IO
- Check which Fs a Hugo deps/config wires in (SourceFs vs PublishFs) before performing operations on it
When it happens
Trigger: Calling Create, Rename, Chmod, Chtimes or Chown on hugofs.NoOpFs, or any Read/Write/Seek/Readdir on a file backed by noOpRegularFileOps (hugofs/noop_fs.go:35-129). It is a panic, not a returned error.
Common situations: Custom Hugo tooling or a module/mount misconfiguration that routes a writable target (publishDir, cache dir) to a NoOpFs-backed composite filesystem; plugin/fork code passing the wrong afero.Fs into a component that later writes.
Related errors
- OpenFunc not set
- failed to save file %q:: %w
- failed to resolve output path %q: %w
- failed to copy %q to %q: %w
- target path "%s" exists but is not a directory
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/1c67356deb73a8bf.json.
Report an issue: GitHub ↗.