Skip to content

Commit 07b5d7b

Browse files
authored
🤖 Collapse WRITE DENIED errors by default (#403)
## Problem File edit tool calls that fail with `WRITE DENIED` errors are displayed expanded by default. These errors happen frequently during normal operation (when a file is modified between read and write) and don't require immediate attention, making the expanded state noisy. ## Solution Modified `FileEditToolCall` to detect `WRITE DENIED` errors via prefix match and start them in collapsed state. Other errors and successful edits remain expanded as before. Users can still manually expand WRITE DENIED errors if they want to see the details. ## Testing - Type checking passes - Logic is straightforward: check error prefix and set initial expansion state _Generated with `cmux`_
1 parent 3445162 commit 07b5d7b

File tree

7 files changed

+20
-14
lines changed

7 files changed

+20
-14
lines changed

docs/AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,8 @@ The IPC layer is the boundary between backend and frontend. Follow these rules t
481481
Notice when you've made the same change many times, refactor to create a shared function
482482
or component, update all the duplicated code, and then continue on with the original work.
483483
When repeating string literals (especially in error messages, UI text, or system instructions), extract them to named constants in a relevant constants/utils file - never define the same string literal multiple times across files.
484+
Before defining a constant, search the codebase to see if it already exists and import it instead.
485+
If a constant exists in a layer-specific location (`services/`, `utils/main/`) but is needed across layers, move it to a shared location (`src/constants/`, `src/types/`) rather than duplicating it.
484486

485487
**Avoid unnecessary callback indirection**: If a hook detects a condition and has access to all data needed to handle it, let it handle the action directly rather than passing callbacks up to parent components. Keep hooks self-contained when possible.
486488

src/components/tools/FileEditToolCall.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useToolExpansion, getStatusDisplay, type ToolStatus } from "./shared/to
2222
import { TooltipWrapper, Tooltip } from "../Tooltip";
2323
import { DiffContainer, DiffRenderer, SelectableDiffRenderer } from "../shared/DiffRenderer";
2424
import { KebabMenu, type KebabMenuItem } from "../KebabMenu";
25+
import { WRITE_DENIED_PREFIX } from "@/types/tools";
2526

2627
type FileEditOperationArgs =
2728
| FileEditReplaceStringToolArgs
@@ -97,7 +98,11 @@ export const FileEditToolCall: React.FC<FileEditToolCallProps> = ({
9798
status = "pending",
9899
onReviewNote,
99100
}) => {
100-
const { expanded, toggleExpanded } = useToolExpansion(true);
101+
// Collapse WRITE DENIED errors by default since they're common and expected
102+
const isWriteDenied = result && !result.success && result.error?.startsWith(WRITE_DENIED_PREFIX);
103+
const initialExpanded = !isWriteDenied;
104+
105+
const { expanded, toggleExpanded } = useToolExpansion(initialExpanded);
101106
const [showRaw, setShowRaw] = React.useState(false);
102107
const [copied, setCopied] = React.useState(false);
103108

src/services/tools/fileCommon.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import type * as fs from "fs";
22
import * as path from "path";
33
import { createPatch } from "diff";
44

5-
/**
6-
* Prefix for all file write error messages.
7-
* This consistent prefix helps models detect when writes fail and need to retry.
8-
*/
9-
export const WRITE_DENIED_PREFIX = "WRITE DENIED, FILE UNMODIFIED:";
5+
// WRITE_DENIED_PREFIX moved to @/types/tools for frontend/backend sharing
106

117
/**
128
* Maximum file size for file operations (1MB)

src/services/tools/file_edit_insert.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as path from "path";
44
import type { FileEditInsertToolResult } from "@/types/tools";
55
import type { ToolConfiguration, ToolFactory } from "@/utils/tools/tools";
66
import { TOOL_DEFINITIONS } from "@/utils/tools/toolDefinitions";
7-
import { validatePathInCwd, WRITE_DENIED_PREFIX } from "./fileCommon";
7+
import { validatePathInCwd } from "./fileCommon";
8+
import { WRITE_DENIED_PREFIX } from "@/types/tools";
89
import { executeFileEditOperation } from "./file_edit_operation";
910

1011
/**

src/services/tools/file_edit_operation.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from "bun:test";
22
import { executeFileEditOperation } from "./file_edit_operation";
3-
import { WRITE_DENIED_PREFIX } from "./fileCommon";
3+
import { WRITE_DENIED_PREFIX } from "@/types/tools";
44

55
const TEST_CWD = "/tmp";
66

src/services/tools/file_edit_operation.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@ import * as fs from "fs/promises";
22
import * as path from "path";
33
import writeFileAtomic from "write-file-atomic";
44
import type { FileEditDiffSuccessBase, FileEditErrorResult } from "@/types/tools";
5+
import { WRITE_DENIED_PREFIX } from "@/types/tools";
56
import type { ToolConfiguration } from "@/utils/tools/tools";
6-
import {
7-
generateDiff,
8-
validateFileSize,
9-
validatePathInCwd,
10-
WRITE_DENIED_PREFIX,
11-
} from "./fileCommon";
7+
import { generateDiff, validateFileSize, validatePathInCwd } from "./fileCommon";
128

139
type FileEditOperationResult<TMetadata> =
1410
| {

src/types/tools.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ export interface FileEditInsertToolArgs {
117117

118118
export type FileEditInsertToolResult = FileEditDiffSuccessBase | FileEditErrorResult;
119119

120+
/**
121+
* Prefix for file write denial error messages.
122+
* This consistent prefix helps both the UI and models detect when writes fail.
123+
*/
124+
export const WRITE_DENIED_PREFIX = "WRITE DENIED, FILE UNMODIFIED:";
125+
120126
export type FileEditToolArgs =
121127
| FileEditReplaceStringToolArgs
122128
| FileEditReplaceLinesToolArgs

0 commit comments

Comments
 (0)