gitbutlerapp/gitbutler · error · anyhow::Error
executable must be specified for non built-ins
Error message
executable must be specified for non built-ins
What it means
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.
Source
Thrown at crates/but-api/src/open/program.rs:745
.map(Into::into)
.unwrap_or_else(|| builtin.executable.clone()),
category: self
.category
.map(Into::into)
.unwrap_or_else(|| builtin.category.clone()),
cli_arg_supplier,
extensions,
})
} else {
let (name, id) = match (self.name, self.id) {
(Some(name), Some(id)) => (name, id),
(Some(name), None) => (name.clone(), name),
(None, Some(id)) => (id.clone(), id),
(None, None) => anyhow::bail!("id or name must be specified"),
};
let Some(executable) = self.executable else {
anyhow::bail!("executable must be specified for non built-ins")
};
Ok(ProgramSpec {
id,
name,
executable: executable.into(),
category: self.category.map(Into::into).unwrap_or_default(),
cli_arg_supplier: CliArgumentSupplier::Custom(CustomCliArgumentSupplier {
open_args: self.open_args,
open_at_line_args: self.open_at_line_args,
}),
extensions,
})
}
}
}
/// The executable to invoke for a program.View on GitHub (pinned to caf1f223d3)
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
Example fix
// before: intent was overriding VS Code, but id is wrong -> treated as new program, no executable
{ "id": "code", "openArgs": ["-r", "{filepath}"] }
// after: correct builtin id -> executable inherited from the builtin
{ "id": "vscode", "openArgs": ["-r", "{filepath}"] } Defensive patterns
Strategy: validation
Validate before calling
// Overrides must match a builtin id exactly; everything else needs an executable
const BUILTIN_IDS = new Set(['vscode', 'intellij', 'zed' /* mirror PROGRAMS in but-api */]);
function isCompleteEntry(e: ProgramEntry): boolean {
const isOverride = typeof e.id === 'string' && BUILTIN_IDS.has(e.id);
return isOverride || e.executable !== undefined;
} Type guard
function isExecutableSpec(x: unknown): x is { type: 'pathExecutable'; path: string } | { type: 'macosApplication'; bundleIdentifier: string } {
if (typeof x !== 'object' || x === null) return false;
const t = x as Record<string, unknown>;
return t.type === 'pathExecutable' && typeof t.path === 'string'
|| t.type === 'macosApplication' && typeof t.bundleIdentifier === 'string';
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- id or name must be specified
- Unknown terminal: {terminal_id}
- Invalid message format
- HTTP Error ${response.statusText}: ${text}
- Please sign in to use GitButler's AI API
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/889a80395bc98dc4.
Report an issue: GitHub.