m1k1o/neko · error

unable to unmarshal %s plugin settings from global settings:

Error message

unable to unmarshal %s plugin settings from global settings: %w

What it means

Returned by filetransfer Manager.isEnabledForSession when the global settings payload for the filetransfer plugin exists but cannot be unmarshalled into the plugin's Settings struct. ErrPluginSettingsNotFound is tolerated (defaults apply); any other unmarshal error aborts with this wrapped error, so it signals malformed global plugin config, not absence of config.

Source

Thrown at server/internal/plugins/filetransfer/manager.go:54

	}
}

type Manager struct {
	logger   zerolog.Logger
	config   *Config
	sessions types.SessionManager
	shutdown chan struct{}
	mu       sync.RWMutex
	fileList []Item
}

func (m *Manager) isEnabledForSession(session types.Session) (bool, error) {
	settings := Settings{
		Enabled: true, // defaults to true
	}
	err := m.sessions.Settings().Plugins.Unmarshal(PluginName, &settings)
	if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) {
		return false, fmt.Errorf("unable to unmarshal %s plugin settings from global settings: %w", PluginName, err)
	}

	profile := Settings{
		Enabled: true, // defaults to true
	}

	err = session.Profile().Plugins.Unmarshal(PluginName, &profile)
	if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) {
		return false, fmt.Errorf("unable to unmarshal %s plugin settings from profile: %w", PluginName, err)
	}

	return m.config.Enabled && (settings.Enabled || session.Profile().IsAdmin) && profile.Enabled, nil
}

func (m *Manager) refresh() (error, bool) {
	// if file transfer is disabled, return immediately without refreshing
	if !m.config.Enabled {
		return nil, false

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Open global settings, find the filetransfer plugin section, and fix field names/types to match the plugin's Settings struct (Enabled bool, etc.)
  2. Validate the settings JSON/YAML against the Settings struct (age/keys/types) before saving
  3. Upgrade-related: migrate old config keys to the current Settings schema
  4. Temporarily delete the plugin settings block so ErrPluginSettingsNotFound applies and defaults (Enabled=true) are used

Example fix

// before (global settings)
plugins:
  filetransfer:
    enabled: "true"   # string, not bool

// after
plugins:
  filetransfer:
    enabled: true
Defensive patterns

Strategy: validation

Validate before calling

raw := settings.Plugins.Get("filetransfer")
if raw != nil {
    if _, ok := raw.(map[string]any); !ok {
        return fmt.Errorf("filetransfer settings must be an object")
    }
    if e, ok := raw.(map[string]any)["enabled"]; ok {
        if _, ok := e.(bool); !ok {
            return fmt.Errorf("filetransfer 'enabled' must be a bool")
        }
    }
}

Type guard

func validFTSettings(s Settings) bool {
    return reflect.TypeOf(s.Enabled).Kind() == reflect.Bool
}

Try / catch

enabled, err := m.isEnabledForSession(session)
if err != nil {
    if errors.Is(err, types.ErrPluginSettingsNotFound) {
        enabled = true
    } else {
        log.Error().Err(err).Msg("check filetransfer plugin settings schema")
        return false
    }
}

Prevention

When it happens

Trigger: A file handler (uploadFileHandler, downloadFileHandler, deleteFileHandler) calls isEnabledForSession; sessions.Settings().Plugins.Unmarshal("filetransfer", &settings) fails with an unmarshal/type error because the global settings JSON/YAML for the plugin has wrong field names or types.

Common situations: Admin hand-edits global config and writes enabled: "yes" (string) instead of a bool, or nests settings under the wrong key; a renamed Settings field after an upgrade leaves stale keys of the wrong type in the config store.

Related errors


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/ba0d5fcb968c5af5. Report an issue: GitHub.