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

  1. Find the panicking call site in the stack trace and identify which component received NoOpFs instead of a real filesystem.
  2. Wire the correct writable Fs (e.g. the OS or destination filesystem) into that component instead of NoOpFs.
  3. 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

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


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/1c67356deb73a8bf.json. Report an issue: GitHub ↗.