gitbutlerapp/gitbutler · error
Invalid response: ${JSON.stringify(result)}
Error message
Invalid response: ${JSON.stringify(result)} What it means
Thrown during hunk-based discard when the matched Modification/Rename change has a typechange flag, meaning the path changed blob kind between the previous state and the worktree (file to symlink, file to submodule/gitlink, etc.). Line hunks only make sense between two text blobs of the same kind, so the library refuses and asks for whole-file mode.
Source
Thrown at apps/desktop/src/lib/ai/ollamaClient.ts:84
* Sends a chat message to the LLM model and returns the response.
*
* @param messages - An array of LLMChatMessage objects representing the chat messages.
* @param options - Optional LLMRequestOptions object for specifying additional options.
* @returns A Promise that resolves to an LLMResponse object representing the response from the LLM model.
*/
private async chat(
messages: Prompt,
options?: OllamaRequestOptions,
): Promise<OllamaChatResponse> {
const result = await this.ollama.chat({
model: this.modelName,
messages,
stream: false,
options,
});
if (!isOllamaChatResponse(result)) {
throw new Error("Invalid response: " + JSON.stringify(result));
}
return result;
}
}
View on GitHub (pinned to caf1f223d3)
Solutions
- Discard the whole file instead: send the DiffSpec with empty hunk_headers, which restores the previous state including its object kind
- Recompute worktree changes so the typechange is visible in the UI and line-level discard is not offered
- If the typechange is unintended (e.g. accidental symlink), resolve it in the worktree first, then redo the hunk discard
Example fix
// before
let specs = vec![DiffSpec { path, previous_path: None, hunk_headers: selected_hunks }];
// after: whole-file restore handles the kind change
let specs = vec![DiffSpec { path, previous_path: None, hunk_headers: Vec::new() }]; Defensive patterns
Strategy: validation
Validate before calling
let wt = but_core::diff::worktree_changes(repo)?;
for spec in &changes {
if spec.hunk_headers.is_empty() { continue; }
if let Some(change) = wt.changes.iter().find(|c| c.path == spec.path) {
if let but_core::TreeStatus::Modification { flags: Some(f), .. } = &change.status {
assert!(!f.is_typechange(), "{} is type-changed: use whole-file mode", spec.path);
}
}
} Type guard
fn is_typechange(status: &but_core::TreeStatus) -> bool {
matches!(status,
but_core::TreeStatus::Modification { flags: Some(f), .. }
| but_core::TreeStatus::Rename { flags: Some(f), .. } if f.is_typechange())
} Try / catch
if let Err(err) = discard_workspace_changes(repo, specs, ctx) {
if err.to_string().contains("Type-changed") {
// fall back: same spec with hunk_headers cleared
} else { return Err(err); }
} Prevention
- Surface typechange status in the UI and offer only whole-file discard for it
- Re-check worktree state after symlink-affecting operations (core.symlinks changes, linkers)
When it happens
Trigger: discard_workspace_changes with a spec containing hunk_headers where the worktree change is TreeStatus::Modification or Rename whose flags satisfy f.is_typechange() - e.g. the user replaced a regular file with a symlink, or a directory became a submodule, then tried to discard selected lines of it.
Common situations: Editor/tooling replacing files with symlinks (dependency dirs, nix-style linking), git submodules added where a file used to be, or a stale diff computed before the type change. Windows dev drives or core.symlinks toggles can also surface file<->symlink typechanges.
Related errors
- When using OpenAI in a bring your own key configuration, you
- Failed to communicate with LM Studio server: ${error instanc
- When using LM Studio, you must provide a valid endpoint
- When using GitButler's API to summarize code, you must be lo
- When using Anthropic in a bring your own key configuration,
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/a29148e3d92661a0.
Report an issue: GitHub.