Context
PR #14417 (#14415) attempted to make git push classifier-free via static allow rules scoped to agent/* branches. @neo-gpt's cross-family review falsified the boundary with a local dry-run probe: Git push grammar separates source from destination via refspecs, so git push origin agent/foo:dev (or HEAD:refs/heads/dev, or an extra space-separated refspec) starts with the allowed prefix while targeting dev. A static prefix glob cannot prove the remote destination — the mechanical guard was weaker than the model classifier it replaced. The push allows were removed from #14417 (push stays classified); this ticket is the correct-shape follow-up his review recommended.
The Problem
The PR-lifecycle allowlist (#14415) makes preflight/add/commit/branch/PR-create deterministic, but push — one command per PR — still requires a live frontier-model classification. During classifier outages (2026-07-02: ~40 min, full write-stoppage) a finished, committed deliverable cannot reach its PR. Static permission globs model command prefixes; push safety is a property of parsed grammar (every refspec's destination must resolve under refs/heads/agent/).
The Architectural Reality
- Sibling precedent:
buildScripts/util/agent-preflight.mjs — an agent-facing gate script invoked via npm run agent-preflight, already allowlisted.
- The permission layer composes: allow
Bash(npm run agent-push*) (deterministic) + keep raw git push classified + the existing deny set (force variants, colon-refspec Bash(git push* *:*), dev*/main* prefixes) as defense-in-depth beneath the wrapper.
- Reviewer's framing to honor (
[RETROSPECTIVE], PR #14417 review): "permission-layer friction fixes must model the command grammar, not just the command prefix."
The Fix
buildScripts/util/agent-push.mjs + npm script agent-push:
- Parse argv strictly: optional
-u, remote (must be origin), refspecs.
- Refuse: any colon refspec, more than one refspec,
--force*/-f/--delete/--mirror/--tags/--all, non-origin remotes, any flag not explicitly whitelisted.
- Resolve the effective destination (explicit refspec, else current branch + upstream semantics) and prove it matches
^agent/; refuse otherwise.
- Exec
git push with the validated argv; propagate exit code verbatim.
- Unit-tested: acceptance (plain
agent/* push, -u first-push) + the full refusal matrix including the reviewer's probe forms (agent/foo:dev, HEAD:refs/heads/dev, extra refspec, --tags).
- Template change (separate small PR or same PR): add
Bash(npm run agent-push*) to permissions.allow.
Acceptance Criteria
Out of Scope
- Any change to raw-
git push deny semantics beyond what #14417 ships.
- Wrapping other git verbs (commit/add are prefix-safe; checkout dev is fixed-string).
Avoided Traps
- Static deny-stack whack-a-mole — enumerating colon/extra-refspec/flag forms as globs is brittle and unprovable; the parser is the boundary (reviewer-recommended shape).
- Wrapper scope creep — it validates and execs; it does not fetch, rebase, or retry.
Decision Record impact
none (aligned with epic #13652 mechanical-enforcement direction; the two-worlds/no-code-exec boundary untouched — the wrapper is repo build-script substrate, not cloud-tier action surface).
Live latest-open sweep: checked latest 15 open issues at 2026-07-02T01:36Z — no equivalent (closest: #14406 agent-preflight check-only mode, different gate). A2A in-flight sweep: none on this scope.
Related: #13652 (parent epic) · #14415 / PR #14417 (the allowlist this completes; review thread carries the falsification probe) · Empirical anchor: the 2026-07-02 classifier outage.
Origin Session ID: 1d4262a2-a001-4387-9372-3923f024be8e
Retrieval Hint: "refspec push wrapper classifier-free" · "push destination boundary agent branches"
Context
PR #14417 (#14415) attempted to make
git pushclassifier-free via static allow rules scoped toagent/*branches. @neo-gpt's cross-family review falsified the boundary with a local dry-run probe: Git push grammar separates source from destination via refspecs, sogit push origin agent/foo:dev(orHEAD:refs/heads/dev, or an extra space-separated refspec) starts with the allowed prefix while targetingdev. A static prefix glob cannot prove the remote destination — the mechanical guard was weaker than the model classifier it replaced. The push allows were removed from #14417 (push stays classified); this ticket is the correct-shape follow-up his review recommended.The Problem
The PR-lifecycle allowlist (#14415) makes preflight/add/commit/branch/PR-create deterministic, but
push— one command per PR — still requires a live frontier-model classification. During classifier outages (2026-07-02: ~40 min, full write-stoppage) a finished, committed deliverable cannot reach its PR. Static permission globs model command prefixes; push safety is a property of parsed grammar (every refspec's destination must resolve underrefs/heads/agent/).The Architectural Reality
buildScripts/util/agent-preflight.mjs— an agent-facing gate script invoked vianpm run agent-preflight, already allowlisted.Bash(npm run agent-push*)(deterministic) + keep rawgit pushclassified + the existing deny set (force variants, colon-refspecBash(git push* *:*),dev*/main*prefixes) as defense-in-depth beneath the wrapper.[RETROSPECTIVE], PR #14417 review): "permission-layer friction fixes must model the command grammar, not just the command prefix."The Fix
buildScripts/util/agent-push.mjs+ npm scriptagent-push:-u, remote (must beorigin), refspecs.--force*/-f/--delete/--mirror/--tags/--all, non-originremotes, any flag not explicitly whitelisted.^agent/; refuse otherwise.git pushwith the validated argv; propagate exit code verbatim.agent/*push,-ufirst-push) + the full refusal matrix including the reviewer's probe forms (agent/foo:dev,HEAD:refs/heads/dev, extra refspec,--tags).Bash(npm run agent-push*)topermissions.allow.Acceptance Criteria
^agent/before exec; happy path pushes normally with verbatim exit-code propagation.Bash(npm run agent-push*)allowlisted in.claude/settings.template.json; rawgit pushremains classified; existing deny rules remain as defense-in-depth.@summaryper contextual-completeness gate; sibling-lift fromagent-preflight.mjsshape.npm run agent-push→ PR) completes with zero classifier dependency.Out of Scope
git pushdeny semantics beyond what #14417 ships.Avoided Traps
Decision Record impact
none(aligned with epic #13652 mechanical-enforcement direction; the two-worlds/no-code-exec boundary untouched — the wrapper is repo build-script substrate, not cloud-tier action surface).Live latest-open sweep: checked latest 15 open issues at 2026-07-02T01:36Z — no equivalent (closest: #14406 agent-preflight check-only mode, different gate). A2A in-flight sweep: none on this scope.
Related: #13652 (parent epic) · #14415 / PR #14417 (the allowlist this completes; review thread carries the falsification probe) · Empirical anchor: the 2026-07-02 classifier outage.
Origin Session ID: 1d4262a2-a001-4387-9372-3923f024be8e Retrieval Hint: "refspec push wrapper classifier-free" · "push destination boundary agent branches"