semaphoreui/semaphore · error
err
Error message
err
What it means
SaveConfig panics with the raw error when the in-memory Semaphore config cannot be marshaled to JSON by config.ToJSON(). This happens only if the JSON encoding of the ConfigType struct fails, which is practically an internal invariant violation since the config struct consists of JSON-serializable fields. The panic is unguarded, so it crashes the setup command with a stack trace.
Solutions
- Check the panic stack trace to identify the unmarshalable config field introduced by recent code changes
- Ensure all fields of the ConfigType struct and nested types are JSON-serializable (no channels, funcs, or cyclic references)
- Upgrade or downgrade Semaphore to a version whose config marshaling works; if the config was populated programmatically, validate it with config.ToJSON() in a recover-wrapped test first
Example fix
// before
bytes, err := config.ToJSON()
if err != nil {
panic(err)
}
// after
bytes, err := config.ToJSON()
if err != nil {
log.Fatalf("could not serialize config to JSON: %v", err)
} Defensive patterns
Strategy: validation
Validate before calling
if _, err := config.ToJSON(); err != nil {
log.Fatalf("config not serializable: %v", err)
} Type guard
func isJSONSerializable(v any) bool {
_, err := json.Marshal(v)
return err == nil
} Try / catch
func safeSave(cfg util.ConfigType) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("SaveConfig panicked: %v", r)
}
}()
SaveConfig(cfg, path)
return nil
} Prevention
- Keep all fields in ConfigType and nested types JSON-serializable
- Add a unit test that marshals a fully populated default config
- Avoid injecting runtime-only values (channels, funcs) into config objects
When it happens
Trigger: Calling doSetup or doRunnerSetup and having config.ToJSON() return an error during JSON marshaling of the assembled config object (e.g. an unsupported value type was injected into the config before marshaling).
Common situations: Custom builds or forks that added a field to the config struct that json.Marshal cannot serialize (channels, funcs, cyclic structures); corrupted intermediate config objects built programmatically via API instead of the interactive wizard.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/33290aa4d11338d8.
Report an issue: GitHub.
Appendix: source
Thrown at cli/setup/setup.go:243
fmt.Printf("Running: mkdir -p %v..\n", configDirectory)
var err error
if _, err = os.Stat(configDirectory); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(configDirectory, 0755)
}
}
if err != nil {
log.Panic("Could not create config directory: " + err.Error())
}
// Marshal config to json
bytes, err := config.ToJSON()
if err != nil {
panic(err)
}
if err = os.WriteFile(configPath, bytes, 0644); err != nil {
panic(err)
}
fmt.Printf("Configuration written to %v..\n", configPath)
return
}
func askValue(prompt string, defaultValue string, item any) {
// Print prompt with optional default value
fmt.Print(prompt)
if len(defaultValue) != 0 {
fmt.Print(" (default " + defaultValue + ")")
}
fmt.Print(": ")
View on GitHub (pinned to 1774ccb71a)