Skip to content

fix: File Explorer not refreshing after the AI Assistant writes a file#20

Merged
archdex-art merged 1 commit into
mainfrom
fix/explorer-not-refreshing-after-assistant-write
Jul 17, 2026
Merged

fix: File Explorer not refreshing after the AI Assistant writes a file#20
archdex-art merged 1 commit into
mainfrom
fix/explorer-not-refreshing-after-assistant-write

Conversation

@archdex-art

Copy link
Copy Markdown
Owner

Bug report

"claude runs well but it claims that it made changes but i cant find any" — screenshot showed a successful write_file tool call ("Created helloworld.py...") and a confirming list_directory call, but the file never appeared in the Explorer sidebar.

Root cause (reproduced live, not assumed)

Verified the backend chain first: the write API and the file-listing API resolve the identical on-disk workspace directory. A direct write+list round trip through the real API confirmed the backend was correct.

Reproduced the actual bug end-to-end in a real browser: stood up a fake OpenAI-compatible server standing in for the LLM (letting the real frontend code run against a deterministic tool call, without spending Anthropic credits), drove a real chat turn through the local-model path, and confirmed via the dev server's request log that after the chat POST completed, the Explorer never issued its usual follow-up GET .../fs?op=list. A direct disk check confirmed the file genuinely existed — the write succeeded, the assistant correctly reported success, but the Explorer silently never refreshed.

Instrumented AssistantPanel.tsx's applyEvent directly and proved the mechanism: it mutated pathsToTouch/shouldMutate inside the setTurns state-updater callback, then read those same variables immediately after calling setTurns(). React does not guarantee a functional state updater has already run by the time setState() returns — especially here, since every event arrives from an async SSE stream-reader loop, not a synchronous React event handler. The live trace proved it: a tool_result for a successful write fired with shouldMutate still false. onMutated() was silently never called.

Fix

Track the currently-running tool's input in a useRef (synchronous, no React-scheduling dependency) instead of deriving it from state-updater side effects. pathsToTouch/shouldMutate are computed BEFORE setTurns is even called.

Verification

  • Reproduced the exact broken behavior live pre-fix, then re-ran the identical scenario post-fix: the newly-created file appeared in the Explorer immediately with zero manual reload.
  • New source-level regression guard (matching this file's existing convention — no React Testing Library/jsdom harness exists in this project).
  • Full suite 233/233, tsc --noEmit clean, lint baseline unchanged, next build clean.

Bug report: "claude runs well but it claims that it made changes but i
cant find any" -- screenshot showed a successful write_file tool call
("Created helloworld.py...") and a confirming list_directory call, but
the file never appeared in the Explorer sidebar.

## Root cause (reproduced live, not assumed)

Verified the entire backend chain first: the workspace write API and the
file-listing API both resolve the identical on-disk workspace directory
(a single DB column, `repos.workspace_dir`), and a direct write+list
round trip through the real API confirmed the backend was correct.

Reproduced the actual bug end-to-end in a real browser: stood up a fake
OpenAI-compatible server standing in for the LLM (letting the exact real
frontend code run against a deterministic "write a file" tool call,
without spending real Anthropic credits), drove a real chat turn through
CodeGraph's actual local-model path, and confirmed via the dev server's
own request log that after the chat POST completed, the file explorer
NEVER issued its usual follow-up `GET .../fs?op=list` request. A direct
disk check confirmed the file genuinely existed -- the write succeeded,
the assistant correctly reported success, but the Explorer silently
never refreshed.

Instrumented `AssistantPanel.tsx`'s `applyEvent` directly (writing to a
`window` global read back via the browser tool, since `page.on('console')`
listeners don't persist across separate browser-tool calls) and proved
the exact mechanism: `applyEvent` computed `pathsToTouch`/`shouldMutate`
by mutating `let` variables *inside* the `setTurns` state-updater
callback, then read those same variables immediately after calling
`setTurns()`. React does not guarantee a functional state updater has
already run by the time `setState()` returns -- especially here, since
every event arrives from an async SSE stream-reader loop, not a
synchronous React event handler. The live trace showed exactly this:
a `tool_result` event for a successful write fired with `shouldMutate`
still `false`, because the updater responsible for setting it hadn't
executed yet at the point it was read. `onMutated()` was silently never
called.

## Fix

Track the currently-running tool's input in a `useRef` (synchronous,
no React-scheduling dependency) instead of deriving it from state-updater
side effects. `pathsToTouch`/`shouldMutate` are now computed BEFORE
`setTurns` is even called, from the ref, eliminating the timing hazard
entirely. `setTurns`'s updater now only updates the rendered block list,
with no side-channel data dependency.

## Verification

- Reproduced the exact broken behavior live (pre-fix): a real write_file
  call succeeded on disk, but the Explorer's follow-up list request never
  fired.
- Re-ran the identical live scenario post-fix: the newly-created file
  appeared in the Explorer immediately with zero manual reload.
- New regression guard in assistant.test.ts (source-level, matching this
  file's existing convention for frontend logic with no React Testing
  Library/jsdom harness in this project): confirms the mutation-flag
  computation is ordered before the setTurns call and uses the new ref,
  not state-updater side effects.
- Full suite 233/233, tsc --noEmit clean, lint baseline unchanged, next
  build clean.
@archdex-art
archdex-art merged commit 3306a4b into main Jul 17, 2026
2 checks passed
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.

1 participant