Repository navigation
feat(agent): one PGX branch per Claude Code subagent, routed automatically - #35
Merged
Merged
Conversation
…cally Claude Code subagents inherit the parent's CLAUDE_CODE_SESSION_ID, so every subagent attached to the parent's branch (benchmark 2026-10-26: three subagents, one database). Subagents now get their own branch without any manual step: - Identity is (project, host, session_id, agent_id). A subagent's branch is agent-<host>-<session8>-<agent8>; the parent keeps its name and records. Collisions fall back to a 16-hex hash of the full identity (max 57 bytes). - Claude Code hooks: SubagentStart ensures the subagent's branch and tells the model its name (no credentials); PreToolUse(Bash) with agent_id answers updatedInput, rewriting the command to `<pgx> agent exec --agent <id> --cwd <project> -- <shell> -c '<cmd>'` so it runs with the branch's DATABASE_URL/PG*; when the branch cannot be ensured it answers permissionDecision: deny with the reason, never allow; SubagentStop terminates the branch's backends and retires it with grace. The session's own SessionStart/CLAUDE_ENV_FILE path is unchanged. - Server-side guards on every agent branch (ALTER DATABASE … SET): client_connection_check_interval (5s), idle_in_transaction_session_timeout (15m), statement_timeout (1h), configurable in .pgx.toml. A killed agent's open transaction aborts instead of committing minutes later. - `pgx agent down` terminates backends before retiring or dropping (the cancellation barrier, reported as backends_terminated/remaining); `--id` without `--host` finds the branch under any host label; `--branch <name>` refuses to act on any other branch; `--agent <id>` everywhere. - `pgx agent exec`: explicit-id branches are labelled `exec`; `--keep` leaves the branch active on its idle TTL (re-attached by the next exec with the same id); JSON gains agent_id, source, created, fresh, method, elapsed_ms, status; PGSERVICE/PGSERVICEFILE cleared in the child; the password only ever travels in the environment. - `pgx agent ls` shows AGENT and deadline_kind; `pgx agent doctor` expects six Claude hooks and summarises the new hook audit log (~/.pgx/agent/hooks.log: ids and outcomes only); `pgx agent install claude` completes a four-hook installation in place. - Tests: tests/agent_subagent.rs (isolation, 20 subagents, concurrency, routing semantics incl. pipes/heredocs/redirects/background/exit codes, fail-closed refusals, stop barrier, guard-based abort of an orphaned transaction, cross-project/session identity, down --id/--branch, no password in any argv), updated exec/install suites, unit tests. - Docs and Skill: automatic subagent isolation, the wrapper form and the permission-rule remedy for prompts/auto mode, guards, --keep semantics, troubleshooting; plugin copy synced; live validation note (3 and 20 real subagents, zero contamination, killed-agent transaction aborted). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Linux CI runners have no /bin/zsh, so `user_shell` fell back to /bin/sh and the assertion failed. /bin/bash exists on every runner. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Why
The 2026-10-26 multi-agent benchmark found that Claude Code subagents all shared the parent session's PGX branch: branches were keyed on
CLAUDE_CODE_SESSION_ID, which every subagent inherits, and nothing handledSubagentStart/SubagentStopor theagent_idClaude Code puts in a subagent'sPreToolUsepayload. Three subagents, one database. A second finding: a killed agent's open transaction committed eight minutes later (PostgreSQL defaults never notice a dead client inside a long statement).What
Identity.
(project, host, session_id, agent_id). A subagent's branch isagent-<host>-<session8>-<agent8>; the parent keeps its name and existing records (no migration). Short-name collisions fall back to a 16-hex hash of the full identity; the longest name is 57 bytes.Hooks (Claude Code).
pgx agent install claudenow writes six entries (an older four-entry install is completed in place):SubagentStart→ the subagent's own branch; its context line names the branch, no credentials.PreToolUse(Bash) withagent_id→updatedInput:<pgx> agent exec --agent <id> --cwd <project> -- <shell> -c '<original>' # pgx-routed <branch>. The original text is one quoted argument (pipes, heredocs, redirects, quoting, background jobs, exit status untouched); the password only travels in the wrapper's environment;PGSERVICEis cleared. When the branch cannot be ensured the hook answerspermissionDecision: denywith the reason. It never answersallow; the rewritten command goes through the host's normal permission flow (the rewrite starts with thepgxinvocation so a prefix allow rule can match it).SubagentStop→ terminate the branch's backends, retire with grace. Parent untouched.Orphaned transactions. Every agent branch gets
client_connection_check_interval=5s,idle_in_transaction_session_timeout=15m,statement_timeout=1h(ALTER DATABASE … SET, configurable or disableable in.pgx.toml; the source is never altered).pgx agent downterminates the branch's backends before retiring/dropping and reportsbackends_terminated/backends_remaining; the guarantee is stated precisely in the docs (a COMMIT past its point of no return still completes; the guards cover the window beforedown).CLI fixes from the benchmark.
pgx agent exec: explicit-id branches are labelledexec(not the command basename),--keepleaves the branch active on its idle TTL and the nextexecwith the same id attaches to it, JSON gainsagent_id/source/created/fresh/method/elapsed_ms/status.pgx agent down --idfinds branches under any host label,--branch <name>refuses any other branch,--agent <id>on every lifecycle command.pgx agent lsshows AGENT anddeadline_kind.pgx agent doctorreports subagent branches and the new hook audit log (~/.pgx/agent/hooks.log, ids and outcomes only).Tests.
tests/agent_subagent.rs(12 black-box tests: per-subagent branches, 20 subagents of one session, concurrent starts, routing with real psql and decoy inheritedDATABASE_URL/PGDATABASE/PGSERVICE, shell semantics, idempotent rewrite, fail-closed refusals, stop barrier with a live backend, guard-based abort of a SIGKILLed client's transaction, cross-project/session identity,down --id/--branch, no password in any argv), updated exec/install suites, unit tests. All agent suites, fmt, clippy (-D warnings), skill and plugin tests pass.Docs and Skill. SKILL.md and the Claude Code / lifecycle / security / troubleshooting references,
docs/pgx/local-dev/{agents,cli}.md, README; plugin copy synced (plugins/sync.sh --checkclean); live validation notedocs/pgx/local-dev/validation/2026-10-26-subagent-isolation-live.md.Live validation (real Claude Code subagents, no ids, no manual steps)
SubagentStartin the same second, every command routed, each branch holds only its own agent's marker/migration/tables, all retired bySubagentStop.SubagentStart(clone incl.: p50 1231 ms, p95 1371 ms, max 1401 ms, all twenty in the same instant), 20/20 workloads in their own branch, 0 contamination, 20/20 retired, 37 s end to end, runtime RSS peak 92 MB, disk fully reclaimed after grace.pg_sleepinsideBEGIN…COMMIT): backend aborted within the 5 s guard, 0 rows committed (before: 2 rows committed 8 min later).Limitations found live
Bash(/path/to/pgx agent exec:*)). Documented in the Skill and troubleshooting.SubagentStop; its branch stays on the idle TTL and the guards protect the data (pgx agent down --agent <id> --nowby hand).agent_typefilter could avoid the clone.🤖 Generated with Claude Code