Skip to content

Fix agent reads to use unsaved Files editor buffers#393

Merged
arul28 merged 5 commits into
mainfrom
cursor/critical-correctness-bugs-fc42
May 29, 2026
Merged

Fix agent reads to use unsaved Files editor buffers#393
arul28 merged 5 commits into
mainfrom
cursor/critical-correctness-bugs-fc42

Conversation

@cursor
Copy link
Copy Markdown
Contributor

@cursor cursor Bot commented May 29, 2026

Bug and impact

When a user edited a file in the Files tab but did not save, agent readFile calls 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 (via window.__ADE_GET_DIRTY_FILE_TEXT__), and createAgentChatService required that callback — but no read path invoked it. All agent file reads used readFileWithinRootSecure only.

Fix

  • Add readAgentAccessibleFileBytes to prefer dirty buffer text when present, then fall back to secure disk read.
  • Route universal 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 typecheck in apps/desktop
Open in Web View Automation 

ADE   Open in ADE  ·  cursor/critical-correctness-bugs-fc42 branch  ·  PR #393

Greptile 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 readAgentAccessibleFileBytes helper 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, /goal slash-command support for Codex threads, and several FilesPage tree-management improvements.

  • Core fix (readAgentAccessibleFileBytes): new utility in utils.ts that resolves a path within the workspace boundary, queries getDirtyFileTextForPath if provided, then falls back to readFileWithinRootSecure; wired through readFileRange, universalTools, buildClaudeV2MessageAsync, and agentChatService.
  • Codex request timeouts: runtime.request() now accepts an optional timeoutMs (defaulting to 30 s); on timeout the request is rejected and the timer is cleared; a isCodexRequestTimeoutError guard triggers runtime teardown if the process was unresponsive.
  • /goal slash commands: parseCodexGoalSlashCommand parses 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

Filename Overview
apps/desktop/src/main/services/shared/utils.ts Adds readAgentAccessibleFileBytes — path-validated dirty-buffer-first read helper — with correct security boundary enforcement and proper async/sync callback handling.
apps/desktop/src/main/services/ai/tools/readFileRange.ts Routes reads through readAgentAccessibleFileBytes; dirty-buffer is only consulted after resolvePathWithinRoot succeeds, so new (never-saved) files still return "File not found" even if a dirty buffer exists.
apps/desktop/src/main/services/chat/agentChatService.ts Wires dirty-buffer reads throughout Codex/Claude/OpenCode attachment paths; adds per-request timeouts with timer cleanup; adds /goal slash-command handling that bypasses the active-turn guard.
apps/desktop/src/main/services/chat/buildClaudeV2Message.ts Adds buildClaudeV2MessageAsync that mirrors the sync version but reads attachment bytes via readAgentAccessibleFileBytes; sync overload kept for test use only.
apps/desktop/src/preload/preload.ts Removes local-runtime fallback from callProjectFileRuntimeActionOr (now remote-or-local only) and adds bypass check during workspace transitions; diff domain switched to remote-only.
apps/desktop/src/renderer/components/files/FilesPage.tsx Refactors tree-refresh concurrency (separate inFlightParents per directory), adds mergeTreePreservingLoadedChildren to avoid clobbering expanded subtrees on root refresh, and adds loadingDirectories UI 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
    end
Loading

Fix All in Cursor Fix All in Codex Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/main/services/shared/utils.ts:476-495
Dirty buffer skipped for new (never-saved) files. `resolvePathWithinRoot` with `allowMissing: false` throws "Path does not exist:" when the file hasn't been written to disk yet, causing the catch block to jump straight to `readFileWithinRootSecure` — which also throws — without ever calling `getDirtyFileTextForPath`. If a user creates a new file in the editor and the agent tries to read it before the first save, it will always get "File not found" even though buffer content is available. Using `allowMissing: true` for the path-validation step, then querying the dirty buffer before falling back to disk, would close this gap.

```suggestion
  const root = path.resolve(args.rootPath);
  let absPath: string;
  try {
    absPath = resolvePathWithinRoot(root, args.resolvedPath, { allowMissing: true });
  } catch {
    // Path escapes root — let readFileWithinRootSecure produce the canonical error.
    return readFileWithinRootSecure(root, args.resolvedPath);
  }

  if (args.getDirtyFileTextForPath) {
    try {
      const dirty = await Promise.resolve(args.getDirtyFileTextForPath(absPath));
      if (typeof dirty === "string") {
        return Buffer.from(dirty, "utf8");
      }
    } catch {
      // Fall back to on-disk content when renderer lookup fails.
    }
  }

  return readFileWithinRootSecure(root, absPath);
```

### Issue 2 of 2
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx:2023-2025
**`isCodexGoalSlashInput` duplicated across the process boundary.** The renderer defines this identical regex in `AgentChatPane.tsx` and the main process defines it again in `agentChatService.ts` (same regex, same function name). If the pattern is ever extended (e.g., to support `/goals` as an alias), both copies must be updated in sync. Consider exporting the predicate from a shared-types module or at minimum adding a co-location comment pointing to the other copy.

Reviews (3): Last reviewed commit: "ship: iteration 2 fix shard test isolati..." | Re-trigger Greptile

@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview May 29, 2026 4:31am

cursoragent and others added 2 commits May 28, 2026 23:43
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>
@arul28 arul28 force-pushed the cursor/critical-correctness-bugs-fc42 branch from a1646c4 to d6f32d7 Compare May 29, 2026 03:44
@arul28
Copy link
Copy Markdown
Owner

arul28 commented May 29, 2026

@copilot review but do not make fixes

@arul28 arul28 marked this pull request as ready for review May 29, 2026 03:44
@arul28 arul28 self-requested a review as a code owner May 29, 2026 03:44
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 29, 2026

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.

Copy link
Copy Markdown
Contributor Author

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • readAgentAccessibleFileBytes correctly 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 buildClaudeV2Message still reads disk only, but production paths now use buildClaudeV2MessageAsync with 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.
  • forceFreshStatus is only exercised in tests today; if git decoration lag is acceptable for speed, consider documenting that tradeoff in the Files feature area.
Open in Web View Automation 

Sent by Cursor Automation: BUGBOT in Versic

Comment thread apps/desktop/src/main/services/chat/agentChatService.ts Outdated
Comment thread apps/desktop/src/main/services/files/fileService.ts
@arul28
Copy link
Copy Markdown
Owner

arul28 commented May 29, 2026

@copilot review but do not make fixes

@arul28
Copy link
Copy Markdown
Owner

arul28 commented May 29, 2026

@copilot review but do not make fixes

@arul28
Copy link
Copy Markdown
Owner

arul28 commented May 29, 2026

@copilot review but do not make fixes

@arul28 arul28 merged commit 9ebdd12 into main May 29, 2026
26 of 27 checks passed
@arul28 arul28 deleted the cursor/critical-correctness-bugs-fc42 branch May 29, 2026 06:09
arul28 added a commit that referenced this pull request May 29, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants