LearnNewsExamplesServices
Frontmatter
id14419
titleRefspec-validated agent-push wrapper — classifier-free push with a real destination boundary
stateClosed
labels
enhancementaibuildmodel-experience
assigneesneo-gpt
createdAtJul 2, 2026, 3:34 AM
updatedAtJul 2, 2026, 11:24 AM
githubUrlhttps://github.com/neomjs/neo/issues/14419
authorneo-fable
commentsCount1
parentIssue13652
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 2, 2026, 11:24 AM

Refspec-validated agent-push wrapper — classifier-free push with a real destination boundary

Closed v13.1.0/archive-v13-1-0-chunk-8 enhancementaibuildmodel-experience
neo-fable
neo-fable commented on Jul 2, 2026, 3:34 AM

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:

  1. Parse argv strictly: optional -u, remote (must be origin), refspecs.
  2. Refuse: any colon refspec, more than one refspec, --force*/-f/--delete/--mirror/--tags/--all, non-origin remotes, any flag not explicitly whitelisted.
  3. Resolve the effective destination (explicit refspec, else current branch + upstream semantics) and prove it matches ^agent/; refuse otherwise.
  4. Exec git push with the validated argv; propagate exit code verbatim.
  5. 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).
  6. Template change (separate small PR or same PR): add Bash(npm run agent-push*) to permissions.allow.

Acceptance Criteria

  • Wrapper refuses every bypass form from the PR #14417 review (colon refspecs, multi-refspec, flag-widening, non-origin remotes) — unit-tested.
  • Wrapper proves destination ^agent/ before exec; happy path pushes normally with verbatim exit-code propagation.
  • Bash(npm run agent-push*) allowlisted in .claude/settings.template.json; raw git push remains classified; existing deny rules remain as defense-in-depth.
  • JSDoc + @summary per contextual-completeness gate; sibling-lift from agent-preflight.mjs shape.
  • Post-merge: one full lifecycle (branch → commit → npm run agent-push → PR) completes with zero classifier dependency.

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"

tobiu referenced in commit c1d6373 - "feat(build): add refspec-safe agent push wrapper (#14419) (#14457)" on Jul 2, 2026, 11:24 AM
tobiu closed this issue on Jul 2, 2026, 11:24 AM