feat(cli): add headless CLI for one-shot agent runs - #76470
Closed
charlesvien wants to merge 5 commits into
Closed
Conversation
Adds packages/cli (@posthog/code-cli, bin posthog-code-cli): run one agent turn against a local repo from the shell — prompt in, assistant output on stdout, exit code out. Supports --permission-mode auto (default) and bypassPermissions, --output text|json, --model, --system-prompt, and stdin prompts. Auth comes from ANTHROPIC_* env vars; question tool calls are parked with the same no-user-available contract as cloud background runs. ClaudeAcpAgent now accepts an injected logger (falling back to its console logger), letting hosts route adapter diagnostics off stdout. Generated-By: PostHog Code Task-Id: 97a6265f-b609-4775-9949-887aca895991
Review follow-ups: adapter error-level diagnostics now reach stderr without --debug (except expected teardown noise); SIGINT cancels the turn and exits 130 deterministically instead of racing the main path; stdout is flushed before the force-exit timer so piped JSON can't truncate; commander choices()/argParser replace hand-rolled validation (and the double error print); session updates and prompt results use the ACP SDK types instead of unknown casts; reject-only permission requests resolve to reject_once; bypassPermissions as non-sandboxed root fails fast with a clear message. Generated-By: PostHog Code Task-Id: 97a6265f-b609-4775-9949-887aca895991
The force-exit backstop now arms unconditionally so a stalled stdout consumer cannot hang the process, and a SIGINT that lands during session setup exits 130 instead of being reset when the turn activates. Generated-By: PostHog Code Task-Id: 97a6265f-b609-4775-9949-887aca895991
Review follow-ups on the headless CLI, plus two fixes in @posthog/agent that it depends on. Adapter diagnostics are no longer forwarded to a host logger by default. Forwarding them routed lines that carry whole payloads (an expanded slash-command's output, raw tool inputs, subprocess stderr) into desktop's electron-log file and OTLP transports, and into cloud's task feed on a session re-init. `Logger.emitLog` calls `onLog` for every level before the debug gate, so `debug: false` did not hold them back. A new `forwardAdapterLogs` opt-in carries them, and only the CLI sets it, where the sink is the operator's own stderr. A plan approval no longer downgrades the session. The unattended policy ranks options by kind, but a plan approval's options are session modes and its only `allow_once` is "manually approve edits", so approving a plan switched an unattended run into an interactive mode and persisted that into the target repo's settings. Plan approvals now take array order, which is the mode the adapter puts first, matching the cloud client. Also: - SIGTERM is handled alongside SIGINT (exit 143), so a supervisor no longer skips teardown and orphans the claude subprocess. - EPIPE on stdout is swallowed, so `… | head -1` stops quietly instead of crashing with a stack trace mid-teardown. - A whitespace-only positional prompt is rejected like a piped one. - The root/bypass guard reads the effective uid, matching `IS_ROOT`. - Reading a prompt from a stdin that never closes is bounded. - The `usage` key stays in the JSON document as null when a turn settles without token counts, and `stopReason`/`usage` use the SDK's types. - An unavailable `--model` id now warns instead of being swapped silently. - `--debug` keeps the stack on an unexpected throw. The unattended permission policy moves to `@posthog/agent/unattended-permission-policy`. It is the third copy in the repo and the only one that ranks by kind; giving it a home is a step toward cloud and desktop adopting it. `runTurn` is split behind a structural connection interface so the signal and exit-code paths are testable, and `ParseResult` is a tagged union so `--help` stops travelling as an error. Adds a README, drops the stale `apps/cli` knip entry, and separates hosts from executable packages in AGENTS.md. Claude-Session: https://claude.ai/code/session_013Gyfq8R69DNpPSV2DBicK5
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports PostHog/code#3798 into the monorepo (PostHog/code
mainis frozen after the desktop import; open PRs there are remade here). Commits preserve the original authorship. Opened as a draft to match the source PR's state.Problem
There's no way to run the agent from a script. The desktop app needs a GUI, and cloud tasks need a sandbox; anyone who wants "prompt in, answer on stdout" (CI checks, shell pipelines, cron jobs) has to shell out to a different tool entirely.
Changes
Adds
packages/cli(@posthog/code-cli, binposthog-code-cli), a headless CLI host that drives the same in-process ACP connection the desktop app uses viaAgentfrom@posthog/agent:--permission-mode auto(default) orbypassPermissions. Interactive modes are rejected since there's no UI to answer prompts. Tool permissions that still reach the client are auto-approved (preferringallow_onceso one-shot runs don't persist allow rules);AskUserQuestioncalls are parked with the same no-user-available contract cloud background runs use, so the model restates the question as text and ends its turn.--output jsonbuffers and emits one{ text, stopReason, usage, sessionId }document for machine consumption.ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN/ANTHROPIC_BASE_URLenv vars, falling back to a stored claude login credential.ClaudeAcpAgentpreviously hardcoded a debug console logger, ignoring the logger injected throughcreateAcpConnection; it now accepts the injected logger (keeping the console fallback), which lets the CLI route adapter diagnostics off stdout behind--debug. Desktop and cloud behavior is unchanged since they don't pass one.The new package lands in
packages/rather than theapps/clislot listed in AGENTS.md because repo settings deny writes there; the AGENTS.md hosts table now points atpackages/cli.How did you test this code?
pnpm --filter @posthog/code-cli test).@posthog/agentsuite after the logger change (1625 passed; one pre-existing flake insettings.test.tsthat passes in isolation).--output jsonshape, clean stdout (zero stderr bytes without--debug, 15 diagnostic lines with it), and exit codes for bad flags/missing prompt.pnpm --filter @posthog/code-cli test:e2e) using the same gateway env contract aspackages/agent/e2e.products/desktop/,pnpm install --frozen-lockfileagainst the regenerated lockfile,pnpm typecheck(25/25 clean including the new package),@posthog/code-clitests (53) and the full@posthog/agentsuite (1781/1782, the one failure being the same pre-existingsettings.test.tsflake, which passes in isolation).Automatic notifications
Docs update
N/A
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
This PR is a port of PostHog/code#3798 (author: haacked) into
products/desktop/, done with Claude Code following the repo's /porting-code-prs skill:git am -3 --directory=products/desktop/over the source patch series, preserving the original commits and authorship. The source lockfile hunk was excluded per the migration contract (the monorepo lockfile carries local security override pins) and regenerated fromproducts/desktop/withpnpm install --lockfile-only; the regenerated diff is scoped to the newpackages/cliimporter and lands as a separate chore commit.Created with PostHog Code