semaphoreui/semaphore · critical

Could not decode configuration!

Error message

Could not decode configuration!

What it means

The JSON config file decoder could not parse config.json into the global Config struct. Semaphore prints 'Could not decode configuration!' and panics with the underlying json error. This means the configuration file is syntactically invalid JSON or has incompatible types for known fields.

Solutions

  1. Validate the file with `jq . /path/to/config.json` (or any JSON linter) and fix syntax errors
  2. If the file is actually YAML, rename it to config.yml or add the .yaml/.yml extension so the YAML decoder is used
  3. Check field types against the ConfigType struct (objects, not strings, for mysql/postgres blocks)
  4. Restore a known-good config.json from backup or `semaphore setup` output

Example fix

// before (config.json with trailing comma)
{"mysql": {"host": "db",},}
// after
{"mysql": {"host": "db"}}
Defensive patterns

Strategy: validation

Validate before calling

// validate JSON config before handing it to semaphore
jq -e . /etc/semaphore/config.json > /dev/null || { echo 'invalid JSON'; exit 1; }

Try / catch

// wrap decodeConfigFile in recover to convert panic to error
defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("config decode failed: %v", r)
    }
}()

Prevention

When it happens

Trigger: decodeConfigFile opens a non-YAML config path and json.NewDecoder(file).Decode(&Config) fails: trailing commas, comments, single quotes, wrong types (e.g. string where object expected), or truncated file.

Common situations: Hand-edited config.json with a stray comma or comment; config written as YAML but named config.json (or missing .yml extension so the YAML branch is skipped); partially-written file from a failed deploy.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/bb15e8735c6f69a6. Report an issue: GitHub.

Appendix: source

Thrown at util/config.go:1894

func exitOnConfigError(msg string) {
	fmt.Println(msg)
	os.Exit(1)
}

func exitOnConfigFileError(err error) {
	if err != nil {
		exitOnConfigError("Cannot Find configuration! Use --config parameter to point to a JSON or YAML file generated by `semaphore setup`.")
	}
}

func decodeConfig(file io.Reader, configPath string) {
	if isYAMLConfig(configPath) {
		decodeConfigYAML(file)
		return
	}
	if err := json.NewDecoder(file).Decode(&Config); err != nil {
		fmt.Println("Could not decode configuration!")
		panic(err)
	}
}

func isYAMLConfig(configPath string) bool {
	ext := strings.ToLower(filepath.Ext(configPath))
	return ext == ".yaml" || ext == ".yml"
}

func decodeConfigYAML(file io.Reader) {
	var raw any
	if err := yaml.NewDecoder(file).Decode(&raw); err != nil {
		fmt.Println("Could not decode configuration!")
		panic(err)
	}
	data, err := json.Marshal(raw)
	if err != nil {
		fmt.Println("Could not decode configuration!")
		panic(err)

View on GitHub (pinned to 1774ccb71a)