gitbutlerapp/gitbutler · error

When using OpenAI in a bring your own key configuration, you

Error message

When using OpenAI in a bring your own key configuration, you must provide a valid token

What it means

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.

Source

Thrown at apps/desktop/src/lib/ai/service.ts:343

		if (modelKind === ModelKind.LMStudio) {
			const lmStudioEndpoint = await this.getLMStudioEndpoint();
			const lmStudioModelName = await this.getLMStudioModelName();

			if (!lmStudioEndpoint) {
				throw new Error("When using LM Studio, you must provide a valid endpoint");
			}

			return new LMStudioClient(lmStudioEndpoint, lmStudioModelName);
		}

		if (modelKind === ModelKind.OpenAI) {
			const openAIModelName = await this.getOpenAIModelName();
			const openAIKey = await this.getOpenAIKey();
			const openAICustomEndpoint = await this.getOpenAICustomEndpoint();

			if (!openAIKey) {
				throw new Error(
					"When using OpenAI in a bring your own key configuration, you must provide a valid token",
				);
			}

			return new OpenAIClient(openAIKey, openAIModelName, openAICustomEndpoint);
		}

		if (modelKind === ModelKind.Anthropic) {
			const anthropicModelName = await this.getAnthropicModelName();
			const anthropicKey = await this.getAnthropicKey();

			if (!anthropicKey) {
				throw new Error(
					"When using Anthropic in a bring your own key configuration, you must provide a valid token",
				);
			}

			return new AnthropicAIClient(anthropicKey, anthropicModelName);

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Re-run worktree_changes and rebuild the discard specs so the current on-disk kind is reflected
  2. Use whole-file discard (empty hunk_headers), which handles restores of symlinks and other kinds
  3. Stop the concurrent writer (build watcher, sync client) that replaced the path, then retry
  4. If the symlink/dir is intended, discard it as a whole file instead of by hunks
Defensive patterns

Strategy: validation

Validate before calling

let wt = but_core::diff::worktree_changes(repo)?; // refresh right before the call
// and immediately before discarding, confirm the path is still a regular file:
let p = repo.workdir().unwrap().join(rela_path);
if let Ok(md) = std::fs::symlink_metadata(&p) {
    assert!(md.is_file(), "{} is no longer a regular file", p.display());
}

Try / catch

if let Err(err) = discard_workspace_changes(repo, specs, ctx) {
    if err.to_string().contains("invalid type") {
        // re-run worktree_changes and rebuild specs; fall back to whole-file discard
    } else { return Err(err); }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/8692d483f648c70d. Report an issue: GitHub.