{"record":{"id":"d7a335d6ed058243","repo":"gitbutlerapp/gitbutler","slug":"failed-to-communicate-with-lm-studio-server-err","errorCode":null,"errorMessage":"Failed to communicate with LM Studio server: ${error instanceof Error ? error.message : String(error)}","messagePattern":"Failed to communicate with LM Studio server: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/src/lib/ai/lmStudioClient.ts","lineNumber":119,"sourceCode":"\t\t\t\t\t\t\t\t\tresult += token;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tconsole.warn(\"Error parsing streaming JSON\", e);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\t// Handle non-streaming response\n\t\t\telse {\n\t\t\t\tconst json = await response.json();\n\t\t\t\treturn json.choices[0]?.message?.content || \"\";\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error calling LM Studio API:\", error);\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to communicate with LM Studio server: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t}\n}\n","sourceCodeStart":101,"sourceCodeEnd":125,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/apps/desktop/src/lib/ai/lmStudioClient.ts#L101-L125","documentation":"Thrown by discard_workspace_changes when a DiffSpec carries hunk_headers but the matched worktree change is an Addition or Deletion. Hunks describe line-level edits relative to a previous file state; an added file has no previous state and a deleted file has no current content, so 'discard these lines' is undefined. The library demands the whole-file mode, i.e. the same path sent with an empty hunk_headers list.","triggerScenarios":"Calling discard_workspace_changes(repo, changes, context_lines) where changes contains a DiffSpec with non-empty hunk_headers whose path matches a worktree change whose TreeStatus is Addition (tracked or untracked new file) or Deletion. Typically the UI offers 'discard selected lines' for a file that is new or deleted instead of modified.","commonSituations":"A diff/selection payload computed when the file was a Modification is replayed after the file was deleted and re-added, or after external git commands (git rm, git checkout, another tool) changed the file's status; stale worktree state in a long-running app; frontends that enable hunk-level discard buttons for all file kinds.","solutions":["Send the spec for that path with an empty hunk_headers Vec (whole-file mode), which restores the previous state (Deletion) or removes/purges the file (Addition)","Re-fetch worktree changes (but_core::diff::worktree_changes) right before discarding and rebuild the DiffSpecs from the fresh statuses","Filter your change list so only Modification/Rename statuses carry hunk_headers before calling the API","If building a UI, disable line-level discard for added/deleted files and fall back to whole-file discard"],"exampleFix":"// before\nlet specs = vec![DiffSpec { path, previous_path: None, hunk_headers: selected_hunks }];\ndiscard_workspace_changes(repo, specs, 3)?;\n\n// after\nlet is_add_or_delete = matches!(status, TreeStatus::Addition { .. } | TreeStatus::Deletion { .. });\nlet specs = vec![DiffSpec {\n    path,\n    previous_path: None,\n    hunk_headers: if is_add_or_delete { Vec::new() } else { selected_hunks },\n}];\ndiscard_workspace_changes(repo, specs, 3)?;","handlingStrategy":"validation","validationCode":"// before calling discard_workspace_changes\nlet wt = but_core::diff::worktree_changes(repo)?;\nfor spec in &changes {\n    if spec.hunk_headers.is_empty() { continue; }\n    if let Some(change) = wt.changes.iter().find(|c| c.path == spec.path) {\n        assert!(!matches!(change.status, but_core::TreeStatus::Addition { .. } | but_core::TreeStatus::Deletion { .. }),\n            \"path {} is added/deleted: use whole-file mode\", spec.path);\n    }\n}","typeGuard":"fn supports_hunk_discard(status: &but_core::TreeStatus) -> bool {\n    matches!(status, but_core::TreeStatus::Modification { .. } | but_core::TreeStatus::Rename { .. })\n}","tryCatchPattern":"match discard_workspace_changes(repo, specs, ctx) {\n    Ok(dropped) => { /* handle unmatched specs */ }\n    Err(err) if err.to_string().contains(\"whole-file mode\") => {\n        // retry the offending path with empty hunk_headers\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Always rebuild DiffSpecs from a fresh worktree_changes() call immediately before discarding","Only attach hunk_headers to Modification/Rename changes","In UIs, gate the 'discard lines' action on the file being modified, not added/deleted"],"tags":["rust","git","gitbutler","diff","hunks","discard"],"backgroundTag":"unsupported-operation-mode","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}