feat(cli): PTY-based Claude backend for interactive billing#82
feat(cli): PTY-based Claude backend for interactive billing#82Jacky040124 wants to merge 3 commits into
Conversation
After June 15, Anthropic separates `claude -p` usage into a dedicated Agent SDK billing pool. This adds ClaudePTYBackend that spawns Claude in an interactive PTY session and reads events from session JSONL, preserving full streaming while being classified as interactive usage. Switch via ALOOK_CLAUDE_BACKEND=pty (default: pipe, no behavior change). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace `import from "bun"` with runtime `globalThis.Bun` access so the bundler does not fail when target !== "bun". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gusye1234
left a comment
There was a problem hiding this comment.
Review: Request Changes
The PTY-based Claude backend concept is sound, but there are several blocking issues:
Blocking:
-
JSONL path encoding is likely incorrect — The code assumes Claude Code encodes cwd by "replacing / with -", but the actual encoding is more complex (percent-encoding or base64 depending on version). If wrong, the JSONL file will never be found and the backend silently produces zero messages. This needs verification against the actual Claude Code implementation.
-
Premature
/exiton firstend_turn— If Claude produces multiple assistant messages (e.g., after tool use), the firstend_turnstop reason triggers/exit, killing the session before tool results are processed and the final response is generated. Multi-turn tool-use flows will break. -
Hardcoded
--permission-mode bypassPermissions— This is extremely permissive. Claude can execute any tool without confirmation. This should at minimum be configurable or inherited from the caller's options, and documented as a security trade-off. -
Monolithic
execute()method (~280 lines) with deeply nested closures. The JSONL parsing, PTY lifecycle, and message queueing should be separate methods for testability and maintainability.
Non-blocking:
❯readiness detection is fragile — will break if Claude Code changes its prompt character.- No input sanitization on
promptbefore writing to PTY stdin. Control characters or/exitin the prompt could break the session. stripAnsiregex is incomplete for all CSI sequences. Consider a well-tested pattern.lastOutputnaming is misleading — it only stores the last text block, not all output.
Recommend addressing items 1-4 before merging.
Summary
ClaudePTYBackendthat spawns Claude Code in an interactive PTY session instead ofclaude -p, so usage is classified as "interactive" rather than "programmatic" under Anthropic's June 15 billing splitALOOK_CLAUDE_BACKEND=ptyenv var; default (pipe) behavior unchangedNew files
src/cli/daemon/agent/claude-pty.ts— ClaudePTYBackend using Bun.Terminal API + session JSONL pollingsrc/cli/daemon/agent/ansi-scanner.ts— Responds to Ink's DA1/DA2/DSR/XTVERSION terminal queries to prevent TUI hangKey design decisions
/exit)~/.claude/projects/<encoded-cwd>/sessions/<id>.jsonlTest plan
ALOOK_CLAUDE_BACKEND=ptyand run a full task: prompt → thinking → tool-use → tool-result → final replyALOOK_CLAUDE_BACKEND=pipe(default) still works unchanged🤖 Generated with Claude Code