Skip to content

ACP stdout is polluted by SessionEnd hook debug logs during shutdown #25345

@tanakaryotadayo-wq

Description

@tanakaryotadayo-wq

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.

Metadata

Metadata

Assignees

Labels

area/non-interactiveIssues related to GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automationtype/bug

Type

No fields configured for Bug.

Projects

Status

Closed

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions