multica-ai/multica · error

remove legacy task state %s: %w

Error message

remove legacy task state %s: %w

What it means

prepareHermesTaskLocalState could not os.RemoveAll a legacy state.db or state.db-* sidecar (journal/WAL/shm) inside the generated overlay. These files are unsafe to reuse across daemon versions (they may be symlinks into the shared store or independent copies), so the migration deletes them and lets Hermes lazily recreate a fresh database; a failed delete fails the overlay.

Source

Thrown at server/internal/daemon/execenv/hermes_home.go:595

		if !fi.Mode().IsRegular() {
			return fmt.Errorf("state marker is not a regular file: %s", marker)
		}
		return nil
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("stat state marker: %w", err)
	}

	entries, err := os.ReadDir(hermesHome)
	if err != nil {
		return fmt.Errorf("read overlay home: %w", err)
	}
	for _, entry := range entries {
		if !isHermesTaskLocalStateEntry(entry.Name()) {
			continue
		}
		path := filepath.Join(hermesHome, entry.Name())
		if err := os.RemoveAll(path); err != nil {
			return fmt.Errorf("remove legacy task state %s: %w", path, err)
		}
	}
	return writeFileAtomic(marker, []byte("task-local Hermes state\n"), 0o600)
}

// linkSharedHermesEntry symlinks dst → src, idempotent across Reuse: an existing
// link already pointing at src is left alone; anything else is removed and
// recreated so the overlay never drifts from the shared home. A dangling source
// (a broken symlink in the user's home) is skipped, not failed. Directories use
// createDirLink and files createFileLink so the Windows copy fallbacks match the
// entry kind.
func linkSharedHermesEntry(src, dst string) error {
	if fi, err := os.Lstat(dst); err == nil {
		if fi.Mode()&os.ModeSymlink != 0 {
			if target, err := os.Readlink(dst); err == nil && target == src {
				return nil
			}
		}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Stop lingering Hermes child processes from previous tasks (check process list for hermes on the same env), then retry.
  2. On Windows, ensure the previous task fully exited before its overlay is reused (wait for handle release).
  3. Fix ownership/permissions on the named path if it is foreign-owned or read-only.
  4. Last resort: delete the entire overlay dir — state.db is task-local scratch and safely regenerable.
Defensive patterns

Strategy: try-catch

Try / catch

if err := prepareHermesTaskLocalState(hermesHome); err != nil {
	var pe *os.PathError
	if errors.As(err, &pe) && strings.Contains(filepath.Base(pe.Path), "state.db") {
		// legacy SQLite files locked or undeletable: reset overlay (state.db is scratch)
		if rmErr := os.RemoveAll(hermesHome); rmErr == nil {
			return prepareHermesTaskLocalState(hermesHome)
		}
	}
	return err
}

Prevention

When it happens

Trigger: state.db is held open by a still-running Hermes process from a previous task; on Windows, SQLite byte-range locks on state.db-shm make deletion fail; read-only or foreign-owned files in the overlay.

Common situations: Upgrading the daemon while an old task's Hermes child is still alive; overlay reused across runs with different UIDs; Windows file-locking semantics on the SQLite sidecars.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/ecf0164b4f355d17. Report an issue: GitHub.