{"record":{"id":"a53a2fa0cfd3a4e2","repo":"sipeed/picoclaw","slug":"command-is-required","errorCode":null,"errorMessage":"command is required","messagePattern":"command is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/agent/hook_mount.go","lineNumber":231,"sourceCode":"\tfor name, spec := range specs {\n\t\tif spec.Enabled {\n\t\t\tnames = append(names, name)\n\t\t}\n\t}\n\tsort.Strings(names)\n\treturn names\n}\n\nfunc processHookOptionsFromConfig(spec config.ProcessHookConfig) (ProcessHookOptions, error) {\n\ttransport := spec.Transport\n\tif transport == \"\" {\n\t\ttransport = \"stdio\"\n\t}\n\tif transport != \"stdio\" {\n\t\treturn ProcessHookOptions{}, fmt.Errorf(\"unsupported transport %q\", transport)\n\t}\n\tif len(spec.Command) == 0 {\n\t\treturn ProcessHookOptions{}, fmt.Errorf(\"command is required\")\n\t}\n\n\topts := ProcessHookOptions{\n\t\tCommand: append([]string(nil), spec.Command...),\n\t\tDir:     spec.Dir,\n\t\tEnv:     processHookEnvFromMap(spec.Env),\n\t}\n\n\tobserveKinds, observeEnabled, err := processHookObserveKindsFromConfig(spec.Observe)\n\tif err != nil {\n\t\treturn ProcessHookOptions{}, err\n\t}\n\topts.Observe = observeEnabled\n\topts.ObserveKinds = observeKinds\n\n\tfor _, intercept := range spec.Intercept {\n\t\tswitch intercept {\n\t\tcase \"before_llm\", \"after_llm\":","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/agent/hook_mount.go#L213-L249","documentation":"A process hook entry is enabled but its command array is empty — there is no executable to spawn. processHookOptionsFromConfig checks len(spec.Command) == 0 right after transport validation, so this is a pure config error caught before any process starts.","triggerScenarios":"hooks.processes.<name>.enabled=true with the command key absent or set to [].","commonSituations":"Enabling a hook placeholder before its command is known; JSON/YAML indentation mistakes putting command at the wrong nesting level so it never lands on the hook spec; renaming the field (e.g. from cmd) during config migrations.","solutions":["Set command to the argv array of the hook process: [\"/path/to/hook\", \"--flag\"]","Disable the entry (enabled=false) until the command exists","Check config indentation so command sits under the correct process hook entry","Re-check the field name is exactly 'command' (array form, not a shell string)"],"exampleFix":"// before\n\"processes\": { \"audit\": { \"enabled\": true, \"observe\": [\"*\"] } }\n\n// after\n\"processes\": { \"audit\": { \"enabled\": true, \"observe\": [\"*\"],\n  \"command\": [\"/usr/local/bin/audit-hook\"] } }","handlingStrategy":"validation","validationCode":"for name, p := range cfg.Hooks.Processes {\n    if p.Enabled && len(p.Command) == 0 {\n        return fmt.Errorf(\"hooks.processes.%s: enabled without a command\", name)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Provide command as an argv array ([\"/path/to/hook\", \"--flag\"]), not a shell string","Keep enabled=false until the command exists","Validate config indentation after hand-edits — mis-nested command keys read as absent"],"tags":["go","picoclaw","hooks","process-hooks","config","validation"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}