charmbracelet/crush · error

docker MCP started but failed to persist configuration: %w

Error message

docker MCP started but failed to persist configuration: %w

What it means

This error is returned by EnableDockerMCP when the Docker MCP tool started successfully but PersistDockerMCPConfig failed to write the configuration to durable storage. The code deliberately rolls back (DisableSingle + RemoveDockerMCPInMemory) so no half-enabled state survives, and joins both the persist error and any rollback disable error into the message.

Source

Thrown at internal/backend/config.go:241

	if err != nil {
		return err
	}

	mcpConfig, err := ws.Cfg.PrepareDockerMCPConfig()
	if err != nil {
		return err
	}

	if err := mcptools.InitializeSingle(ctx, config.DockerMCPName, ws.Cfg); err != nil {
		disableErr := mcptools.DisableSingle(ws.Cfg, config.DockerMCPName)
		ws.Cfg.RemoveDockerMCPInMemory()
		return fmt.Errorf("failed to start docker MCP: %w", errors.Join(err, disableErr))
	}

	if err := ws.Cfg.PersistDockerMCPConfig(mcpConfig); err != nil {
		disableErr := mcptools.DisableSingle(ws.Cfg, config.DockerMCPName)
		ws.Cfg.RemoveDockerMCPInMemory()
		return fmt.Errorf("docker MCP started but failed to persist configuration: %w", errors.Join(err, disableErr))
	}

	publishConfigChanged(ws)
	return nil
}

// DisableDockerMCP closes the Docker MCP client, removes the
// configuration, and persists the change.
func (b *Backend) DisableDockerMCP(workspaceID string) error {
	ws, err := b.GetWorkspace(workspaceID)
	if err != nil {
		return err
	}

	if err := mcptools.DisableSingle(ws.Cfg, config.DockerMCPName); err != nil {
		return fmt.Errorf("failed to disable docker MCP: %w", err)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check filesystem permissions and free space where the config is persisted.
  2. Inspect the wrapped persist error (errors.Join) to distinguish the write failure from rollback failure.
  3. Ensure only one process at a time modifies the workspace config (file locking or app-level serialization).
  4. After fixing, re-run EnableDockerMCP — the rollback should have left a clean state.
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(configDir)
if err != nil || !info.IsDir() {
    return fmt.Errorf("config dir missing: %s", configDir)
}
test := filepath.Join(configDir, ".write-test")
if err := os.WriteFile(test, nil, 0o644); err != nil {
    return fmt.Errorf("config dir not writable: %w", err)
}
os.Remove(test)

Type guard

func IsPersistErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to persist configuration")
}

Try / catch

if err := backend.EnableDockerMCP(ctx, wsID, cfg); err != nil {
    if strings.Contains(err.Error(), "failed to persist") {
        // startup succeeded; state was rolled back — safe to retry after fixing FS
    }
    return err
}

Prevention

When it happens

Trigger: Calling EnableDockerMCP where InitializeSingle succeeds but ws.Cfg.PersistDockerMCPConfig(mcpConfig) returns an error — e.g. the config file is unwritable, the target directory is read-only, disk full, or a concurrent write corrupted the config store.

Common situations: Running in a container or sandbox with a read-only filesystem; insufficient permissions on ~/.config or the workspace .crush directory; disk quota exceeded; config file locked by another process.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/2033dbbcc4730204. Report an issue: GitHub.