{"record":{"id":"889a80395bc98dc4","repo":"gitbutlerapp/gitbutler","slug":"executable-must-be-specified-for-non-built-ins","errorCode":null,"errorMessage":"executable must be specified for non built-ins","messagePattern":"executable must be specified for non built-ins","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api/src/open/program.rs","lineNumber":745,"sourceCode":"                    .map(Into::into)\n                    .unwrap_or_else(|| builtin.executable.clone()),\n                category: self\n                    .category\n                    .map(Into::into)\n                    .unwrap_or_else(|| builtin.category.clone()),\n                cli_arg_supplier,\n                extensions,\n            })\n        } else {\n            let (name, id) = match (self.name, self.id) {\n                (Some(name), Some(id)) => (name, id),\n                (Some(name), None) => (name.clone(), name),\n                (None, Some(id)) => (id.clone(), id),\n                (None, None) => anyhow::bail!(\"id or name must be specified\"),\n            };\n\n            let Some(executable) = self.executable else {\n                anyhow::bail!(\"executable must be specified for non built-ins\")\n            };\n\n            Ok(ProgramSpec {\n                id,\n                name,\n                executable: executable.into(),\n                category: self.category.map(Into::into).unwrap_or_default(),\n                cli_arg_supplier: CliArgumentSupplier::Custom(CustomCliArgumentSupplier {\n                    open_args: self.open_args,\n                    open_at_line_args: self.open_at_line_args,\n                }),\n                extensions,\n            })\n        }\n    }\n}\n\n/// The executable to invoke for a program.","sourceCodeStart":727,"sourceCodeEnd":763,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-api/src/open/program.rs#L727-L763","documentation":"Same conversion path (UserDefinedProgramSpec::try_into_program_spec): when the spec does NOT override a builtin (its id matches no PROGRAMS entry), an `executable` value is required so but-api knows what to launch. Its absence bails. The subtle trap: entries intended as builtin overrides that miss the builtin id (typo, renamed builtin id across versions) silently fall into this 'new program' branch and then fail for the missing executable instead of overriding.","triggerScenarios":"A spec with name/id but no \"executable\" object; or an override entry whose id does not exactly match a builtin id (e.g. \"code\" vs \"vscode\", trailing whitespace, renamed across versions), so the builtin-merge branch is skipped and executable becomes mandatory.","commonSituations":"Hand-written override that only sets openArgs/openAtLineArgs; GitButler version change renaming a builtin program id so an old override stops matching; partially synced or truncated JSON.","solutions":["If you meant a brand-new program: add the \"executable\" block (pathExecutable or macOS bundle identifier)","If you meant to override a builtin: fix the \"id\" to match the builtin id exactly (check PROGRAMS in crates/but-api/src/open/program.rs); overrides inherit the builtin's executable","Otherwise delete the broken entry - it is silently filtered out of program listings anyway"],"exampleFix":"// before: intent was overriding VS Code, but id is wrong -> treated as new program, no executable\n{ \"id\": \"code\", \"openArgs\": [\"-r\", \"{filepath}\"] }\n\n// after: correct builtin id -> executable inherited from the builtin\n{ \"id\": \"vscode\", \"openArgs\": [\"-r\", \"{filepath}\"] }","handlingStrategy":"validation","validationCode":"// Overrides must match a builtin id exactly; everything else needs an executable\nconst BUILTIN_IDS = new Set(['vscode', 'intellij', 'zed' /* mirror PROGRAMS in but-api */]);\nfunction isCompleteEntry(e: ProgramEntry): boolean {\n  const isOverride = typeof e.id === 'string' && BUILTIN_IDS.has(e.id);\n  return isOverride || e.executable !== undefined;\n}","typeGuard":"function isExecutableSpec(x: unknown): x is { type: 'pathExecutable'; path: string } | { type: 'macosApplication'; bundleIdentifier: string } {\n  if (typeof x !== 'object' || x === null) return false;\n  const t = x as Record<string, unknown>;\n  return t.type === 'pathExecutable' && typeof t.path === 'string'\n      || t.type === 'macosApplication' && typeof t.bundleIdentifier === 'string';\n}","tryCatchPattern":null,"preventionTips":["When writing a builtin override, copy the builtin id verbatim from list_programs() output rather than typing it","Re-check overrides after upgrading GitButler - renamed builtin ids turn overrides into standalone programs that then need an executable","If an entry is not an override, always include the executable block in the same edit"],"tags":["configuration","user-defined-programs","builtin-override","validation","settings"],"backgroundTag":"missing-required-config-field","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}