{"record":{"id":"8692d483f648c70d","repo":"gitbutlerapp/gitbutler","slug":"when-using-openai-in-a-bring-your-own-key-configur","errorCode":null,"errorMessage":"When using OpenAI in a bring your own key configuration, you must provide a valid token","messagePattern":"When using OpenAI in a bring your own key configuration, you must provide a valid token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/src/lib/ai/service.ts","lineNumber":343,"sourceCode":"\n\t\tif (modelKind === ModelKind.LMStudio) {\n\t\t\tconst lmStudioEndpoint = await this.getLMStudioEndpoint();\n\t\t\tconst lmStudioModelName = await this.getLMStudioModelName();\n\n\t\t\tif (!lmStudioEndpoint) {\n\t\t\t\tthrow new Error(\"When using LM Studio, you must provide a valid endpoint\");\n\t\t\t}\n\n\t\t\treturn new LMStudioClient(lmStudioEndpoint, lmStudioModelName);\n\t\t}\n\n\t\tif (modelKind === ModelKind.OpenAI) {\n\t\t\tconst openAIModelName = await this.getOpenAIModelName();\n\t\t\tconst openAIKey = await this.getOpenAIKey();\n\t\t\tconst openAICustomEndpoint = await this.getOpenAICustomEndpoint();\n\n\t\t\tif (!openAIKey) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"When using OpenAI in a bring your own key configuration, you must provide a valid token\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn new OpenAIClient(openAIKey, openAIModelName, openAICustomEndpoint);\n\t\t}\n\n\t\tif (modelKind === ModelKind.Anthropic) {\n\t\t\tconst anthropicModelName = await this.getAnthropicModelName();\n\t\t\tconst anthropicKey = await this.getAnthropicKey();\n\n\t\t\tif (!anthropicKey) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"When using Anthropic in a bring your own key configuration, you must provide a valid token\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn new AnthropicAIClient(anthropicKey, anthropicModelName);","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/apps/desktop/src/lib/ai/service.ts#L325-L361","documentation":"Raised in the hunk-discard path after the new worktree content is reconstructed: it stats the path (no symlink following) and requires a regular file, because the result must be run through the filter pipeline and written back as file content. If the path is now a directory, symlink, or otherwise not a plain file, the operation bails rather than corrupt it.","triggerScenarios":"discard_workspace_changes with hunks for a path that, between the diff computation and the write, was replaced by a symlink or directory; or the change itself tracks a symlink/directory while hunks were supplied from stale data. gix::index::fs::Metadata::from_path_no_follow returning a non-file md triggers it.","commonSituations":"Concurrent processes (build tools, other agents, watchers) swapping files for symlinks or directories mid-operation; monorepo tooling creating dir-over-file; a diff computed before the swap and replayed after.","solutions":["Re-run worktree_changes and rebuild the discard specs so the current on-disk kind is reflected","Use whole-file discard (empty hunk_headers), which handles restores of symlinks and other kinds","Stop the concurrent writer (build watcher, sync client) that replaced the path, then retry","If the symlink/dir is intended, discard it as a whole file instead of by hunks"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let wt = but_core::diff::worktree_changes(repo)?; // refresh right before the call\n// and immediately before discarding, confirm the path is still a regular file:\nlet p = repo.workdir().unwrap().join(rela_path);\nif let Ok(md) = std::fs::symlink_metadata(&p) {\n    assert!(md.is_file(), \"{} is no longer a regular file\", p.display());\n}","typeGuard":null,"tryCatchPattern":"if let Err(err) = discard_workspace_changes(repo, specs, ctx) {\n    if err.to_string().contains(\"invalid type\") {\n        // re-run worktree_changes and rebuild specs; fall back to whole-file discard\n    } else { return Err(err); }\n}","preventionTips":["Do not run discard while build tools/watchers may swap files for symlinks or directories","Refresh the diff right before writing, keep the read-to-write window short"],"tags":["rust","git","gitbutler","filesystem","race-condition","symlink","discard"],"backgroundTag":"file-kind-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}