{"record":{"id":"33290aa4d11338d8","repo":"semaphoreui/semaphore","slug":"err-33290a","errorCode":null,"errorMessage":"err","messagePattern":"err","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/setup/setup.go","lineNumber":243,"sourceCode":"\n\tfmt.Printf(\"Running: mkdir -p %v..\\n\", configDirectory)\n\n\tvar err error\n\n\tif _, err = os.Stat(configDirectory); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\terr = os.MkdirAll(configDirectory, 0755)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tlog.Panic(\"Could not create config directory: \" + err.Error())\n\t}\n\n\t// Marshal config to json\n\tbytes, err := config.ToJSON()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif err = os.WriteFile(configPath, bytes, 0644); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Printf(\"Configuration written to %v..\\n\", configPath)\n\treturn\n}\n\nfunc askValue(prompt string, defaultValue string, item any) {\n\t// Print prompt with optional default value\n\tfmt.Print(prompt)\n\tif len(defaultValue) != 0 {\n\t\tfmt.Print(\" (default \" + defaultValue + \")\")\n\t}\n\tfmt.Print(\": \")\n","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/cli/setup/setup.go#L225-L261","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nbytes, err := config.ToJSON()\nif err != nil {\n\tpanic(err)\n}\n// after\nbytes, err := config.ToJSON()\nif err != nil {\n\tlog.Fatalf(\"could not serialize config to JSON: %v\", err)\n}","handlingStrategy":"validation","validationCode":"if _, err := config.ToJSON(); err != nil {\n\tlog.Fatalf(\"config not serializable: %v\", err)\n}","typeGuard":"func isJSONSerializable(v any) bool {\n\t_, err := json.Marshal(v)\n\treturn err == nil\n}","tryCatchPattern":"func safeSave(cfg util.ConfigType) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"SaveConfig panicked: %v\", r)\n\t\t}\n\t}()\n\tSaveConfig(cfg, path)\n\treturn nil\n}","preventionTips":["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"],"tags":["go","cli","json-serialization","panic"],"backgroundTag":"json-marshal-failed","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}