gemini-cli - ✅(Solved) Fix bug(acp): AcpFileSystemService.normalizeFileSystemError misses structured ENOENT codes and common not-found phrasings [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
google-gemini/gemini-cli#26448Fetched 2026-05-05 06:03:35
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Author
Timeline (top)
commented ×1cross-referenced ×1labeled ×1

Error Message

When gemini runs as an ACP agent and the host returns a file-system error from a delegated readTextFile call, AcpFileSystemService.normalizeFileSystemError (packages/cli/src/acp/acpFileSystemService.ts) decides whether the error means "file not found" by substring-matching the human-readable message against four exact phrases: The structured JSON-RPC code field on the error response (-32002 = RESOURCE_NOT_FOUND per the ACP spec) is ignored. Common phrasings produced by ACP hosts that don't match any of those four substrings — for example not_found: file not found, no such file or directory, or file_not_found — are classified as generic file-system errors instead of ENOENT. The user-visible symptom: write_file on a new path internally calls readTextFile first to check whether the file exists. When the read returns a not-found error that fails the substring check, the write tool sees a generic FS error rather than the expected ENOENT, treats the path as "exists but unreadable", and either retries or reports an opaque failure. From outside, the agent reasoning loop appears to hallucinate success after several retries because no write actually lands. This was discovered wiring gemini --experimental-acp into a custom ACP host. When the host's read errors carried message not_found: file not found and code -32002, every write_file call on a non-existent path silently failed (multi-minute hangs, watchdog restarts). Reformatting the host's error message to include the literal substring ENOENT made every write start succeeding immediately — confirming the substring matcher is the load-bearing path. (Discovered while running gemini --experimental-acp against a custom ACP host implementing fs.readTextFile / fs.writeTextFile delegation. The bug reproduces against any host whose not-found error message doesn't contain one of the four hardcoded substrings.)

  • Reproduces on every write_file call to a non-existent path when the host's not-found error doesn't match one of the four hardcoded substrings.
  • PR #26439 addresses this by checking error.code === -32002 first and expanding the substring set.

Root Cause

The user-visible symptom: write_file on a new path internally calls readTextFile first to check whether the file exists. When the read returns a not-found error that fails the substring check, the write tool sees a generic FS error rather than the expected ENOENT, treats the path as "exists but unreadable", and either retries or reports an opaque failure. From outside, the agent reasoning loop appears to hallucinate success after several retries because no write actually lands.

Fix Action

Fixed

PR fix notes

PR #26439: fix(acp): recognise structured ENOENT codes and broader not-found phrasings

Description (problem / solution / changelog)

Problem

AcpFileSystemService.normalizeFileSystemError classifies a read failure as file-not-found by string-matching the message against four exact substrings:

errorMessage.includes('Resource not found') ||
errorMessage.includes('ENOENT') ||
errorMessage.includes('does not exist') ||
errorMessage.includes('No such file')

JSON-RPC error responses also carry a structured code field, but it was ignored. Common phrasings that don't match any of those four substrings — notably not_found (snake_case) and file not found (no capitalisation, no "No such") — fall through to the generic throw err branch.

The downstream effect is severe. The write-file tool calls readTextFile first to check existence, and a non-ENOENT-shaped error short-circuits the call site with error: { code: FILE_WRITE_FAILURE } rather than fileExists: false (see packages/core/src/tools/write-file.ts). The agent's reasoning loop then commonly hallucinates "the tool succeeded" and reports success in the final stop_reason: end_turn response — a silent failure pattern that's hard to detect from outside the CLI.

Fix

This PR makes normalizeFileSystemError:

  1. Prefer the structured signal: when the rejection carries code: 'ENOENT', recognise it as ENOENT regardless of message wording. Cheapest signal, matches what well-behaved JSON-RPC servers should emit.
  2. Relax the legacy substring matcher: also accept not_found and file not found phrasings. These are common in JSON-RPC servers in the wild and appear in the agent-client-protocol ecosystem in particular.

The existing four substrings are preserved for backward compatibility.

Tests added

In packages/cli/src/acp/acpFileSystemService.test.ts:

  • it.each over the two new message variants (not_found snake_case and file not found lowercase).
  • A separate test for the structured-code path: server throws Object.assign(new Error('opaque server message'), { code: 'ENOENT' }) and the matcher recognises it.

npm run test:ci -- src/acp/acpFileSystemService13 passed (3 new + 10 existing).

How this surfaced

Discovered while running gemini-cli in ACP mode against a third-party stdio JSON-RPC server. The server raised fs/read_text_file not_found: file not found: <path> for a non-existent file. None of the four current substrings matched, so every gemini-cli write to that worktree silently produced no file changes. After this patch a single-file write smoke against the same server goes from ~9 minutes with three server restarts (recovered by chance) to ~36 seconds clean.

Compatibility

  • Behaviour for the four pre-existing substrings is unchanged.
  • The two new substrings (not_found, file not found) and the structured code === 'ENOENT' path are strictly additive — they only convert errors that previously fell through to throw err into ENOENT-coded errors, never the reverse. No risk of regression for existing servers that already produce one of the four recognised messages.

Fixes #26448.

Changed files

  • packages/cli/src/acp/acpFileSystemService.test.ts (modified, +76/-0)
  • packages/cli/src/acp/acpFileSystemService.ts (modified, +33/-6)

Code Example

errorMessage.includes('Resource not found') ||
errorMessage.includes('ENOENT') ||
errorMessage.includes('does not exist') ||
errorMessage.includes('No such file')

---

gemini --version: 0.40.1
Platform:        darwin arm64 (Darwin 25.4.0)
Mode:            --experimental-acp
RAW_BUFFERClick to expand / collapse

What happened?

When gemini runs as an ACP agent and the host returns a file-system error from a delegated readTextFile call, AcpFileSystemService.normalizeFileSystemError (packages/cli/src/acp/acpFileSystemService.ts) decides whether the error means "file not found" by substring-matching the human-readable message against four exact phrases:

errorMessage.includes('Resource not found') ||
errorMessage.includes('ENOENT') ||
errorMessage.includes('does not exist') ||
errorMessage.includes('No such file')

The structured JSON-RPC code field on the error response (-32002 = RESOURCE_NOT_FOUND per the ACP spec) is ignored. Common phrasings produced by ACP hosts that don't match any of those four substrings — for example not_found: file not found, no such file or directory, or file_not_found — are classified as generic file-system errors instead of ENOENT.

The user-visible symptom: write_file on a new path internally calls readTextFile first to check whether the file exists. When the read returns a not-found error that fails the substring check, the write tool sees a generic FS error rather than the expected ENOENT, treats the path as "exists but unreadable", and either retries or reports an opaque failure. From outside, the agent reasoning loop appears to hallucinate success after several retries because no write actually lands.

This was discovered wiring gemini --experimental-acp into a custom ACP host. When the host's read errors carried message not_found: file not found and code -32002, every write_file call on a non-existent path silently failed (multi-minute hangs, watchdog restarts). Reformatting the host's error message to include the literal substring ENOENT made every write start succeeding immediately — confirming the substring matcher is the load-bearing path.

#14190 attempted a similar fix earlier but was closed un-merged on 2026-01-24. #17588 was closed on the assumption that #14190 had landed, so the symptom is still live in 0.40.1.

What did you expect to happen?

normalizeFileSystemError should:

  1. Inspect the JSON-RPC code field first and return ENOENT for -32002 (RESOURCE_NOT_FOUND) regardless of message phrasing.
  2. Recognize broader human-readable not-found phrasings (no such file, file_not_found, not_found, cannot find) so that hosts using non-canonical wording still produce ENOENT downstream.

Client information

<details> <summary>Client Information</summary>
gemini --version: 0.40.1
Platform:        darwin arm64 (Darwin 25.4.0)
Mode:            --experimental-acp

(Discovered while running gemini --experimental-acp against a custom ACP host implementing fs.readTextFile / fs.writeTextFile delegation. The bug reproduces against any host whose not-found error message doesn't contain one of the four hardcoded substrings.)

</details>

Login information

Google account.

Anything else we need to know?

  • Reproduces on every write_file call to a non-existent path when the host's not-found error doesn't match one of the four hardcoded substrings.
  • PR #26439 addresses this by checking error.code === -32002 first and expanding the substring set.
  • Related: #17588 (same area, closed stale); #14190 (earlier fix attempt, closed un-merged).

extent analysis

TL;DR

The issue can be fixed by modifying the AcpFileSystemService.normalizeFileSystemError function to prioritize checking the JSON-RPC code field for -32002 and expanding the substring matching for human-readable not-found error messages.

Guidance

  • Inspect the JSON-RPC code field first and return ENOENT for -32002 (RESOURCE_NOT_FOUND) regardless of message phrasing.
  • Expand the substring matching to recognize broader human-readable not-found phrasings, such as no such file, file_not_found, not_found, and cannot find.
  • Consider applying the changes proposed in PR #26439, which addresses this issue by checking error.code === -32002 first and expanding the substring set.
  • Verify the fix by testing write_file calls on non-existent paths with different error message phrasings from the ACP host.

Example

if (error.code === -32002) {
  return 'ENOENT';
} else if (
  errorMessage.includes('Resource not found') ||
  errorMessage.includes('ENOENT') ||
  errorMessage.includes('does not exist') ||
  errorMessage.includes('No such file') ||
  errorMessage.includes('no such file') ||
  errorMessage.includes('file_not_found') ||
  errorMessage.includes('not_found') ||
  errorMessage.includes('cannot find')
) {
  return 'ENOENT';
}

Notes

The proposed fix assumes that the ACP host returns a JSON-RPC code field with a value of -32002 for RESOURCE_NOT_FOUND errors. If this is not the case, additional modifications may be necessary.

Recommendation

Apply the workaround by modifying the AcpFileSystemService.normalizeFileSystemError function to prioritize checking the JSON-RPC code field and expanding the substring matching, as proposed in PR #26439. This should fix the issue and allow write_file calls to succeed as

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

gemini-cli - ✅(Solved) Fix bug(acp): AcpFileSystemService.normalizeFileSystemError misses structured ENOENT codes and common not-found phrasings [1 pull requests, 1 comments, 2 participants]