{"record":{"id":"40318b9e1a6e663d","repo":"gitbutlerapp/gitbutler","slug":"id-or-name-must-be-specified","errorCode":null,"errorMessage":"id or name must be specified","messagePattern":"id or name must be specified","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api/src/open/program.rs","lineNumber":741,"sourceCode":"                id: id.clone(),\n                name: self.name.unwrap_or_else(|| builtin.name.clone()),\n                executable: self\n                    .executable\n                    .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        }","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-api/src/open/program.rs#L723-L759","documentation":"User-defined programs are deserialized from the user-defined programs JSON file in the app config dir (USER_DEFINED_PROGRAMS_FILENAME) into UserDefinedProgramSpec values, then converted via try_into_program_spec(). When a spec matches no builtin program id and has neither id nor name, there is no identifier to register the program under, so conversion bails. Through list_programs()/list_editors() this is actually a soft failure: the entry is dropped with a 'Failed to decode user defined program specification' warning, so the program just silently disappears from the list.","triggerScenarios":"A user-defined-programs JSON entry like {\"executable\": {\"type\": \"pathExecutable\", ...}} with both \"id\" and \"name\" absent or null. Direct callers of UserDefinedProgramSpec::try_into_program_spec get the Err; the settings/listing path filters it out with a tracing::warn.","commonSituations":"Hand-editing the user-defined programs file and omitting \"name\"; a settings-sync or migration tool writing partial objects; older file formats that assumed a different required-field set.","solutions":["Add a \"name\" field - when only name is given, id defaults to the same value","Or add an explicit unique \"id\" (id alone is also accepted)","Re-run list_programs()/list_editors() and confirm the entry now appears; check logs for the warn if it still does not"],"exampleFix":"// before (user-defined-programs.json entry)\n{ \"executable\": { \"type\": \"pathExecutable\", \"path\": \"/usr/local/bin/helix\" } }\n\n// after\n{ \"name\": \"Helix\", \"executable\": { \"type\": \"pathExecutable\", \"path\": \"/usr/local/bin/helix\" } }","handlingStrategy":"validation","validationCode":"// Validate a user-defined programs entry before writing/using it\nfunction hasIdentifier(entry: { id?: unknown; name?: unknown }): boolean {\n  return typeof entry.id === 'string' && entry.id.length > 0\n      || typeof entry.name === 'string' && entry.name.length > 0;\n}\nconst valid = entries.filter(hasIdentifier);","typeGuard":"interface ProgramEntry { id?: string; name?: string; executable?: unknown; openArgs?: string[]; openAtLineArgs?: string[]; category?: string; extensions?: string[] }\nfunction isUsableProgramEntry(e: ProgramEntry): boolean {\n  return typeof e.id === 'string' || typeof e.name === 'string';\n}","tryCatchPattern":null,"preventionTips":["Always set at least 'name' when writing user-defined program entries - id then defaults to it","After editing the user-defined programs file, run list_programs/list_editors and check logs for 'Failed to decode user defined program specification' warns (entries fail silently there)","Validate entries with a schema before persisting, not after the app filters them out"],"tags":["configuration","user-defined-programs","serde","validation","settings"],"backgroundTag":"missing-required-config-field","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}