charmbracelet/crush · error

failed to persist docker mcp configuration: %w

Error message

failed to persist docker mcp configuration: %w

What it means

Wraps an error from ConfigStore.SetConfigField while persisting the staged Docker MCP config under the `mcp.docker` key of the global config file. Preparation (in-memory) succeeded, but writing the global config failed — usually a filesystem or serialization problem.

Source

Thrown at internal/config/docker_mcp.go:105

		return MCPConfig{}, fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds")
	}

	mcpConfig := DockerMCPConfig()
	// In-memory only; persistence happens in PersistDockerMCPConfig.
	s.mutateInMemory(func(c *Config) {
		if c.MCP == nil {
			c.MCP = make(map[string]MCPConfig)
		}
		c.MCP[DockerMCPName] = mcpConfig
	})
	return mcpConfig, nil
}

// PersistDockerMCPConfig persists a previously prepared Docker MCP
// configuration to the global config file.
func (s *ConfigStore) PersistDockerMCPConfig(mcpConfig MCPConfig) error {
	if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil {
		return fmt.Errorf("failed to persist docker mcp configuration: %w", err)
	}
	return nil
}

// EnableDockerMCP adds Docker MCP configuration and persists it.
func (s *ConfigStore) EnableDockerMCP() error {
	mcpConfig, err := s.PrepareDockerMCPConfig()
	if err != nil {
		return err
	}
	if err := s.PersistDockerMCPConfig(mcpConfig); err != nil {
		return err
	}
	return nil
}

// DisableDockerMCP removes Docker MCP configuration and persists the change.
func (s *ConfigStore) DisableDockerMCP() error {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check permissions on the global config file/directory (ls -l ~/.config/crush/) and fix ownership/permissions.
  2. Validate the existing global config file is valid JSON/TOML; fix or back it up and let Crush regenerate it.
  3. Free disk space if the filesystem is full.
  4. Retry EnableDockerMCP after fixing the filesystem; the in-memory staged config is re-prepared automatically.

Example fix

// before
$ ls -l ~/.config/crush/crush.json
-rw-r--r-- 1 root root ... // owned by root
// after
$ sudo chown -R $USER ~/.config/crush
$ crush docker mcp enable
Defensive patterns

Strategy: try-catch

Validate before calling

cfgPath := globalConfigPath()
if f, err := os.OpenFile(cfgPath, os.O_WRONLY, 0o644); err != nil {
    return fmt.Errorf("global config not writable (%s): %w", cfgPath, err)
} else {
    f.Close()
}

Type guard

func isPermissionErr(err error) bool {
    return errors.Is(err, os.ErrPermission) || errors.Is(err, syscall.EACCES) || errors.Is(err, syscall.EROFS)
}

Try / catch

if err := store.PersistDockerMCPConfig(mcpCfg); err != nil {
    var pErr *fs.PathError
    if errors.As(err, &pErr) && isPermissionErr(pErr) {
        log.Fatalf("cannot write %s: check ownership (was a sudo run involved?)", pErr.Path)
    }
    return err
}

Prevention

When it happens

Trigger: Calling PersistDockerMCPConfig (directly or via EnableDockerMCP) when the global config file cannot be written: read-only filesystem, permission denied on the config file/directory, corrupted config JSON, or disk full.

Common situations: ~/.config/crush/ owned by root after a sudo run; config file made read-only; home directory on a read-only mount or full disk; JSON schema mismatch after a Crush version upgrade.

Related errors


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