gate4agent Debugging Notes
April 10, 2026 · View on GitHub
Running list of known issues, gotchas, and diagnostic recipes per CLI. Updated as problems are hit and fixed.
General diagnostic flow
If a session produces no events:
- Is the CLI binary on
PATH? Run it manually first (claude --version,codex --version, etc.). - Is the CLI logged in? gate4agent doesn't handle auth. Each CLI manages its own credentials.
- Capture raw stdout — before blaming the parser, spawn the exact argv gate4agent uses and pipe to a file. Compare against the fixture NDJSON in
tests/for that CLI. - Check for interactive prompts — headless mode must not prompt. Codex needs
--full-auto --skip-git-repo-check(gate4agent adds these automatically). - Check exit code —
SessionEnd { result: "exit_code=N", is_error: ... }tells you if the child crashed.exit_code=0without real events usually means the CLI wrote something we don't parse.
Per-CLI issues
Claude Code
- Prompt is delivered via stdin, not argv. If stdin is closed before the prompt is written, Claude will exit with no output.
--dangerously-skip-permissionsis always passed. Without it, headless mode blocks on permission prompts.--append-system-promptcontaining double quotes: gate4agent's Windows shell-wrapper (argv_to_windows_shell_string) handles escaping, but complex prompts with nested quotes can still break. If that happens, write the prompt to a file and reference it.- Resume session id: UUID string from previous session's
SessionStartevent. Must be exact.
Codex
- Production bug fixed in 0.2.0:
CodexNdjsonParserwas readingitem.get("output")for command results but Codex actually emitsaggregated_output. Any 0.1.x consumer would see empty shell output. Upgrade to 0.2.0+ if you care about tool results. - Interactive hangs without
--full-auto: fixed in 0.2.0. If you still see hangs, check you're on 0.2.0+. --skip-git-repo-check: fixed in 0.2.0. Without it, Codex refuses to run in non-git directories.- Resume shape:
codex exec resume <session_id> --json .... Note the sub-sub-command — this is why gate4agent uses function-per-CLI builders instead of a declarative spec. - No terminal event: Codex doesn't emit any
session_end-equivalent. gate4agent synthesizesSessionEndwhen the child process exits. If you see twoSessionEndevents per session, the parser is double-counting — please file an issue with the raw NDJSON. assistant_messagevsagent_message: both naming conventions are accepted by the parser (0.2.0). Older Codex versions used one, newer use the other.- Session storage:
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl— useful for debugging without re-running.
Gemini
- No resume in pipe mode: the Gemini CLI doesn't expose
--resumefor-pheadless mode.SpawnOptions::resume_session_idis silently ignored. If you need multi-turn with Gemini, bundle the prior context into the prompt itself. --yolowas removed in 0.2.0 spawn args — not needed for--output-format stream-jsonand only adds stderr noise.- Session storage:
~/.gemini/tmp/<hash>/chats/— reference for debugging.
OpenCode (sst/opencode)
- 5-event schema:
step_start,tool_use,text,step_finish,error. Some versions usetool_useas an alias forstep_start— parser accepts both. - Session id prefix:
ses_XXXX. Parser tracks this automatically; use withSpawnOptions::resume_session_idto resume. - Parser is doc-based. If real output differs, file an issue with raw stdout.
- Don't confuse with
charmbracelet/crushoropencode-ai/opencode. gate4agent targetssst/opencodev1.4.0+. - Source docs: https://opencode.ai/docs/cli/
Transport-level issues
SessionEnd synthesis
- Exactly one
SessionEndis guaranteed per session: either the parser emitted one, or the reader loop synthesizes one on child exit. If you see zero or two, that's a bug — please file it. - Synthetic SessionEnd format:
{ result: "exit_code=N", cost_usd: None, is_error: N != 0 }.
Windows-specific
- Spawn uses
cmd /C <shell_string>on Windows. The shell string is built byargv_to_windows_shell_stringwhich wraps each token in"..."with\"escaping. If you pass a prompt containing backticks,%var%, or^escapes,cmd.exemay interpret them — useextra_argscautiously. - PTY path uses ConPTY. If you see corrupt output in PTY mode, verify your Windows version supports ConPTY (Windows 10 1809+).
Reader thread deadlocks
- Reader thread blocks on
child.stdout.read_line(). If the CLI never closes stdout and never exits, the thread hangs forever. Kill the session viaTransportSession::kill()orPipeSession::kill()to force cleanup. - On kill, gate4agent drops the stdin handle first (which usually causes the CLI to exit cleanly), then waits up to 2s, then
child.kill()if needed.
Test runner
# All unit tests
cd gate4agent && cargo test --lib
# Builder argv parity tests
cargo test --test builder_argv
# Live integration tests (require a CLI installed and logged in)
cargo test --test pipe_live -- --ignored --nocapture
# SessionEnd synthesis unit tests
cargo test --lib pipe::session::tests
If any test fails on a clean checkout with a released version, file an issue with:
- OS + version
cargo --version- Full test output
- Installed CLI versions (
claude --version,codex --version, etc.) if running CLI-level tests
Reporting a bug
- Reproduce with
RUST_LOG=gate4agent=trace - Capture the raw NDJSON (or PTY screen) from the CLI directly
- File an issue on GitHub with: CLI name + version, gate4agent version, OS, raw output, expected vs actual event sequence
Windows spawn: cmd /C vs bash fallback
gate4agent detects whether a CLI has a .cmd wrapper on PATH:
- If
.cmdexists:cmd /C program.cmd arg1 arg2(npm-installed tools) - If no
.cmd:bash -c 'program arg1 ...'(bash scripts, native binaries)
Why not join args into a shell string? cmd.exe /C has bizarre quote-stripping rules: if the first char after /C is ", cmd may strip enclosing quotes and break the inner command. Passing args individually via .arg() lets Windows CreateProcess handle quoting correctly.
Why /S /C "..." doesn't work? Tested — cmd.exe still misinterprets nested quotes in certain edge cases (e.g., prompts with periods and colons). The individual-args approach is more reliable.