Fix agent reads to use unsaved Files editor buffers#393
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
getDirtyFileTextForPath was wired from the renderer but never consulted on agent read paths. readFile tools and chat attachments always read disk, so agents saw stale content when the user had unsaved edits in the Files tab. Add readAgentAccessibleFileBytes and route universal readFile, Claude/Codex attachments, and orchestration tools through it. Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
a1646c4 to
d6f32d7
Compare
|
@copilot review but do not make fixes |
|
Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
There was a problem hiding this comment.
PR Review
Scope: 20 file(s), +981 / −154
Verdict: Minor issues
This PR fixes the core gap where agent readFile, attachments, and orchestration tools read from disk while the Files editor holds unsaved buffers, by routing reads through readAgentAccessibleFileBytes and the existing getDirtyFileTextForPath bridge. The same change set also improves Files tree concurrency, Codex RPC timeouts, and local preload routing. The dirty-read path is well tested; the main follow-ups are around git decoration freshness and aggressive Codex teardown on slow inline commands.
🐛 Functionality
[Medium] Git decorations can stay blank after Files tab load
File: apps/desktop/src/main/services/files/fileService.ts:L549-L564
Issue: When the git status cache is cold or past TTL, listTree returns immediately with emptyGitStatusSnapshot (or a stale snapshot) while refreshGitStatusSnapshot runs in the background. Nothing in the renderer re-calls listTree when that background refresh finishes, so the tree rendered on first open can keep missing change indicators on root-level files until the user triggers another full refresh (file watcher, workspace switch, rename, etc.).
Repro: Open the Files tab on a repo with modified files after the 5s cache has expired (or on first service use). Observe root entries without status dots; wait for background git status to complete — dots still absent until you cause another root refreshTree().
Fix: Either await foreground git status for root listTree (as before), or emit an event / callback when refreshGitStatusSnapshot completes and have FilesPage call refreshTree() so decorations update without user action.
[Medium] Codex inline slash timeout tears down the whole runtime
File: apps/desktop/src/main/services/chat/agentChatService.ts:L9374-L9376
Issue: A 10s timeout on inline Codex RPCs (e.g. /review) calls teardownRuntime(managed, "handle_close"), which drops the app-server connection and pending state. A slow but healthy Codex response can therefore force a full runtime restart and interrupt an in-flight turn, not just fail the slash command.
Repro: Start a Codex session and run an inline slash command whose RPC exceeds CODEX_INLINE_COMMAND_TIMEOUT_MS (10_000). On timeout, the session runtime is torn down instead of surfacing a recoverable error.
Fix: Treat inline-command timeouts like other RPC failures (log, complete the slash with an error) without teardownRuntime unless the connection is already dead; reserve teardown for transport/process failures.
Notes
readAgentAccessibleFileBytescorrectly resolves paths within the workspace before consulting dirty buffers, and the regression test blocks dirty lookup from bypassing the root boundary — good pattern to keep.- Sync
buildClaudeV2Messagestill reads disk only, but production paths now usebuildClaudeV2MessageAsyncwith dirty lookup; no production gap found. - Preload routing local file/diff IPC in-process (and blocking mutating file ops during project switch) looks intentional and is covered by new preload tests.
forceFreshStatusis only exercised in tests today; if git decoration lag is acceptable for speed, consider documenting that tradeoff in the Files feature area.
Sent by Cursor Automation: BUGBOT in Versic
|
@copilot review but do not make fixes |
|
@copilot review but do not make fixes |
|
@copilot review but do not make fixes |
In runtime-backed (local-runtime-daemon) mode — the production default —
the in-process AppContext only populates a handful of services; diffService,
fileService, laneService, agentChatService, etc. are null because the real
ones live in the daemon. Several preload routes / IPC handlers dereferenced
those null services for local projects.
- diff.{getChanges,getFile,getFilePatch}: route via
callProjectRuntimeActionIfBound again (regressed by #393 to remote-only),
so local projects hit the daemon instead of a null ctx.diffService. This
is what produced "ade.diff.getChanges: Cannot read properties of null
(reading 'getChanges')" on every lane in the Lanes tab.
- callProjectFileRuntimeActionOr: restore the local-runtime fallback before
the in-process local() path (also regressed by #393), fixing the 13 file
ops that would otherwise hit a null ctx.fileService.
- lanes.openFolder handler: resolve the worktree path via the runtime pool
(lane.list) when ctx.laneService is null, keeping shell.openPath local.
- agentChatReadTranscript handler: guard against a null ctx.agentChatService.
Tests: restore the two daemon-routing preload tests #393 deleted and convert
its in-process diff test into a daemon-routing regression guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


Bug and impact
When a user edited a file in the Files tab but did not save, agent
readFilecalls and chat attachments still read from disk. The agent could plan and apply changes against stale content, leading to wrong edits and conflicts when the user saved their in-progress work.Root cause
PR #372 introduced renderer dirty-buffer tracking and main-process
getDirtyFileTextForPath(viawindow.__ADE_GET_DIRTY_FILE_TEXT__), andcreateAgentChatServicerequired that callback — but no read path invoked it. All agent file reads usedreadFileWithinRootSecureonly.Fix
readAgentAccessibleFileBytesto prefer dirty buffer text when present, then fall back to secure disk read.readFile, orchestration tools, Claude SDK messages, Codex staging, and inline attachment blocks through the helper.Validation
npx vitest run src/main/services/ai/tools/readFileRange.test.ts(includes dirty-buffer regression)npm run typecheckinapps/desktopGreptile Summary
This PR routes all agent file reads (tool reads, orchestration inputs, Claude SDK messages, Codex staging, and inline chat attachment blocks) through a new
readAgentAccessibleFileByteshelper that checks the renderer's dirty-buffer cache first before falling back to a secure on-disk read. It also adds Codex per-request timeouts with automatic runtime teardown on timeout,/goalslash-command support for Codex threads, and severalFilesPagetree-management improvements.readAgentAccessibleFileBytes): new utility inutils.tsthat resolves a path within the workspace boundary, queriesgetDirtyFileTextForPathif provided, then falls back toreadFileWithinRootSecure; wired throughreadFileRange,universalTools,buildClaudeV2MessageAsync, andagentChatService.runtime.request()now accepts an optionaltimeoutMs(defaulting to 30 s); on timeout the request is rejected and the timer is cleared; aisCodexRequestTimeoutErrorguard triggers runtime teardown if the process was unresponsive./goalslash commands:parseCodexGoalSlashCommandparses sub-commands (show,clear,status, implicit objective), bypasses the active-turn guard so goal updates can arrive mid-turn, and optionally starts a follow-up turn when the session is idle.Confidence Score: 4/5
Safe to merge; the dirty-buffer routing is correctly gated behind workspace-boundary validation and the fallback to disk is always present.
The core change is sound: path validation happens before the dirty-buffer callback is ever invoked, so no workspace boundary can be bypassed via a crafted buffer. All callers that previously used readFileWithinRootSecure directly have been updated. The one gap — new files that don't yet exist on disk are still invisible to the agent — is a pre-existing limitation and doesn't worsen any current behavior. Codex timeout machinery cleans up timers correctly. The FilesPage tree-merge preserves full previously-loaded subtrees at the root level.
The readAgentAccessibleFileBytes fallback path in utils.ts silently skips the dirty-buffer lookup for files that exist only in the editor buffer — worth a follow-up if new-file creation in the editor should also be agent-readable before first save.
Important Files Changed
readAgentAccessibleFileBytes— path-validated dirty-buffer-first read helper — with correct security boundary enforcement and proper async/sync callback handling.readAgentAccessibleFileBytes; dirty-buffer is only consulted afterresolvePathWithinRootsucceeds, so new (never-saved) files still return "File not found" even if a dirty buffer exists.buildClaudeV2MessageAsyncthat mirrors the sync version but reads attachment bytes viareadAgentAccessibleFileBytes; sync overload kept for test use only.callProjectFileRuntimeActionOr(now remote-or-local only) and adds bypass check during workspace transitions; diff domain switched to remote-only.mergeTreePreservingLoadedChildrento avoid clobbering expanded subtrees on root refresh, and addsloadingDirectoriesUI state.Sequence Diagram
sequenceDiagram participant Agent as Agent Tool / Chat participant RAFB as readAgentAccessibleFileBytes participant RPWR as resolvePathWithinRoot participant DB as getDirtyFileTextForPath<br/>(renderer bridge) participant Disk as readFileWithinRootSecure<br/>(disk) Agent->>RAFB: rootPath, resolvedPath RAFB->>RPWR: validate path in root (allowMissing:false) alt path escapes root RPWR-->>RAFB: throws Path escapes root RAFB->>Disk: re-validates, throws else path ok RPWR-->>RAFB: absPath validated RAFB->>DB: getDirtyFileTextForPath(absPath) alt dirty buffer present DB-->>RAFB: string content RAFB-->>Agent: Buffer.from(content, utf8) else no dirty buffer or error DB-->>RAFB: undefined or throws RAFB->>Disk: readFileWithinRootSecure(root, absPath) Disk-->>RAFB: Buffer RAFB-->>Agent: Buffer end endPrompt To Fix All With AI
Reviews (3): Last reviewed commit: "ship: iteration 2 fix shard test isolati..." | Re-trigger Greptile