gemini-cli - ✅(Solved) Fix ACP stdout is polluted by SessionEnd hook debug logs during shutdown [2 pull requests, 1 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#25345Fetched 2026-04-14 05:55:51
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
labeled ×2

In ACP mode, stdout can be polluted by non-JSON debug lines when project hooks are enabled and the ACP session closes. The ACP client then logs Failed to parse JSON message: for hook planner/runner debug lines even though the hook itself succeeds.

Examples of leaked lines:

  • Deduplicated hook: ...
  • Created execution plan for SessionEnd: ...
  • Expanding hook command: ...
  • Hook execution for SessionEnd: ...

Root Cause

ACP stdout should remain strict NDJSON. Even when the hook behavior is correct, the client sees protocol corruption/noise and reports parser warnings.

More importantly, this blocks practical use of Gemini CLI as a dependable ACP server for external clients, orchestrators, and IDE integrations. The broader goal here is not just to remove noise in a test run, but to make Gemini CLI ACP mode reliable enough to be used as a real integration surface.

Fix Action

Fix / Workaround

This looks like a cleanup ordering bug between the global console patch and ACP shutdown:

  • packages/cli/src/gemini.tsx installs a global ConsolePatcher and registers consolePatcher.cleanup with registerCleanup(...).
  • packages/cli/src/acp/acpClient.ts also installs an ACP-specific ConsolePatcher.
  • ACP shutdown awaits connection.closed.finally(runExitCleanup).
  • During runExitCleanup(), the global cleanup restores the console before SessionEnd hook debug logging is fully done.
  • The later debugLogger.debug(...) calls from hook planning / execution then fall back to stdout.

Keep a console patch active for the entire ACP shutdown lifecycle.

PR fix notes

PR #25388: fix: keep console patch active in ACP mode to prevent stdout pollution

Description (problem / solution / changelog)

Summary

Fixes #25345

During ACP shutdown, the ConsolePatcher cleanup was registered (line 369) before the SessionEnd hook handler (line 548) in gemini.tsx. When runExitCleanup() iterated cleanup functions in registration order, console.* reverted to stdout before hooks fired, causing debugLogger.debug() output to corrupt the ACP NDJSON stream.

Fix: Skip registering consolePatcher.cleanup when argv.acp or argv.experimentalAcp is set. The console stays patched (routing to stderr) for the entire ACP process lifecycle. This is safe because ACP mode exits via return runAcpClient(...), after which main() returns and the process exits.

Non-ACP modes (interactive, non-interactive) are completely unaffected.

Test plan

  • All 37 existing tests in gemini.test.tsx pass
  • Pre-commit hooks pass (prettier, eslint)
  • TypeScript compiles (no new errors; pre-existing errors in unrelated files only)
  • Verified argv.acp and argv.experimentalAcp match the same flags used in config.ts:751 (isAcpMode)

Changed files

  • packages/cli/src/gemini.tsx (modified, +7/-1)

PR #25400: [codex] Keep ACP console patch active during shutdown

Description (problem / solution / changelog)

Summary

  • keep the global console patch active for the full ACP shutdown lifecycle
  • treat the global patcher as non-interactive in ACP mode so stray console log/info output stays suppressed
  • add a regression test covering ACP startup cleanup registration

Root Cause

ACP mode awaited runExitCleanup() after the connection closed, but main() had already registered the global ConsolePatcher.cleanup. During shutdown that cleanup could restore the original console before SessionEnd hook debug logging finished, letting hook planner/runner messages leak onto stdout.

Testing

  • npm run build --workspace @google/gemini-cli-core
  • npm run test --workspace @google/gemini-cli -- src/gemini_cleanup.test.tsx
  • npx eslint packages/cli/src/gemini.tsx packages/cli/src/gemini_cleanup.test.tsx

Closes #25345

Changed files

  • packages/cli/src/gemini.tsx (modified, +5/-2)
  • packages/cli/src/gemini_cleanup.test.tsx (modified, +37/-0)
RAW_BUFFERClick to expand / collapse

Summary

In ACP mode, stdout can be polluted by non-JSON debug lines when project hooks are enabled and the ACP session closes. The ACP client then logs Failed to parse JSON message: for hook planner/runner debug lines even though the hook itself succeeds.

Examples of leaked lines:

  • Deduplicated hook: ...
  • Created execution plan for SessionEnd: ...
  • Expanding hook command: ...
  • Hook execution for SessionEnd: ...

Why this matters

ACP stdout should remain strict NDJSON. Even when the hook behavior is correct, the client sees protocol corruption/noise and reports parser warnings.

More importantly, this blocks practical use of Gemini CLI as a dependable ACP server for external clients, orchestrators, and IDE integrations. The broader goal here is not just to remove noise in a test run, but to make Gemini CLI ACP mode reliable enough to be used as a real integration surface.

Repro

  1. Configure project-level hooks with a SessionEnd matcher for exit.
  2. Start Gemini CLI in ACP mode from the bundle, for example bundle/gemini.js --acp.
  3. Connect an ACP client over acp.ndJsonStream(...).
  4. Run a prompt that triggers at least one tool call and then close stdin / close the ACP connection.
  5. Observe client-side parse warnings from @agentclientprotocol/sdk/dist/stream.js with Failed to parse JSON message:.

I reproduced this while validating ACP hook behavior with a SessionEnd(exit) hook and an AfterTool(write_file) hook.

Expected

Only NDJSON protocol messages should be emitted on stdout in ACP mode. Debug / feedback text should stay on stderr.

That guarantee is important if Gemini CLI ACP mode is intended to be used as a real server boundary rather than only as an internal/dev surface.

Actual

During shutdown, hook planning/execution debug lines are emitted to stdout, so the ACP client tries to parse them as NDJSON and reports parse failures.

Diagnosis

This looks like a cleanup ordering bug between the global console patch and ACP shutdown:

  • packages/cli/src/gemini.tsx installs a global ConsolePatcher and registers consolePatcher.cleanup with registerCleanup(...).
  • packages/cli/src/acp/acpClient.ts also installs an ACP-specific ConsolePatcher.
  • ACP shutdown awaits connection.closed.finally(runExitCleanup).
  • During runExitCleanup(), the global cleanup restores the console before SessionEnd hook debug logging is fully done.
  • The later debugLogger.debug(...) calls from hook planning / execution then fall back to stdout.

Candidate fix

Keep a console patch active for the entire ACP shutdown lifecycle.

One local fix that stopped the parser warnings was:

  • treat ACP as non-interactive for the global patch setup, and
  • do not register the global ConsolePatcher.cleanup in ACP mode, so the console remains redirected until ACP shutdown is fully complete.

The ACP-local patch can still be restored at the very end.

Validation

After applying that fix locally, these passed:

  • npm run bundle
  • npm run test:integration:sandbox:none -- ./acp-success-memory-hooks.test.ts
  • npm run test:integration:sandbox:none -- ./acp-telemetry.test.ts

The stderr warnings are still visible, but ACP stdout stays clean and the client no longer logs NDJSON parse failures.

extent analysis

TL;DR

To fix the issue, keep a console patch active for the entire ACP shutdown lifecycle by treating ACP as non-interactive for the global patch setup and not registering the global ConsolePatcher.cleanup in ACP mode.

Guidance

  • Identify the console patching logic in packages/cli/src/gemini.tsx and packages/cli/src/acp/acpClient.ts to understand how console output is being redirected.
  • Verify that the ConsolePatcher.cleanup is not being registered in ACP mode to prevent the console from being restored too early.
  • Ensure that the ACP-local patch is restored at the very end of the ACP shutdown lifecycle to maintain clean stdout output.
  • Test the fix by running the provided integration tests, such as acp-success-memory-hooks.test.ts and acp-telemetry.test.ts, to validate that ACP stdout remains clean and the client no longer logs NDJSON parse failures.

Example

No code snippet is provided as the issue does not contain sufficient information to generate a specific code example.

Notes

The provided candidate fix suggests that treating ACP as non-interactive for the global patch setup and not registering the global ConsolePatcher.cleanup in ACP mode may resolve the issue. However, further testing and validation are necessary to ensure that this fix does not introduce any unintended side effects.

Recommendation

Apply the workaround by keeping a console patch active for the entire ACP shutdown lifecycle, as this has been shown to resolve the issue in local testing. This approach ensures that ACP stdout remains clean and the client no longer logs NDJSON parse failures, making Gemini CLI ACP mode more reliable for use as a real integration surface.

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