Skip to content

feat(cli): belt the role panes' git pushes and gh - #37

Merged
Bakhomious merged 1 commit into
mainfrom
step/belt
Nov 4, 2026
Merged

Bakhomious merged 1 commit into
mainfrom
step/belt

Conversation

@Bakhomious

Copy link
Copy Markdown
Contributor

What

A role's pane now runs its copilot with a second belt behind the guard. The belt is a set of settings in the pane's environment, which every command and script there inherits, however it runs. They make git's pushes out of the machine fail, and gh too (decision 55, fact 38, risk 2). The user picked the git and gh parts (option A) through nudge on 2026-11-04. The rest is the lead's call, flagged in decision 55.

  • git. After the GIT_CONFIG_COUNT settings that baton inherits, which it keeps, the belt adds:

    • one pushInsteadOf rule for each character that can start a URL that leaves the machine (a–z, A–Z, 0–9, [). Each sends the push to baton-blocks-push::<url>, a remote helper that doesn't exist, so git fails with 128 before any network.
    • a rule that keeps file:// URLs local;
    • from the repo's remotes, read when the pane starts: an exact rule for each remote URL that leaves, which beats the user's shorter rules, and an empty pushurl for each remote whose push URL leaves, which clears them (git ≥ 2.46). Older gits get an insteadOf rule for that push URL instead.

    Pushes to local paths, fetches, pulls and clones don't change.

  • gh. GH_CONFIG_DIR=/dev/null/baton-blocks-gh, so every gh command fails before it starts and never reaches the Keychain. An empty folder wouldn't do: gh would still find its Keychain login.

  • Tokens. The pane goes without GH_TOKEN, GITHUB_TOKEN and their enterprise forms. If COPILOT_GITHUB_TOKEN isn't set, copilot gets the value of GH_TOKEN (or else GITHUB_TOKEN) as COPILOT_GITHUB_TOKEN, so its own login keeps working. A user whose copilot logs in only through gh runs copilot login once (README).

  • Refusals. baton pane builds the belt before it waits. It fails if git can't read the config or its version, or if GIT_CONFIG_COUNT is one that git reads but Go doesn't (such as " 1").

  • The orchestrator's pane has no belt, since the user approves its outward actions there (decision 54).

  • New in internal/repo: RemoteURLs (the raw remote.*.url and pushurl values, before insteadOf rewrites) and ClearsPushURLs (git ≥ 2.46).

Costs:

  • Pushes to a relative path that starts with a letter or digit (git push backup.git) fail.
  • So do pushes to a git server on the machine by URL (http://localhost/…).
  • No gh reads in role panes.

Gaps (risk 2):

  • git send-pack, likely git http-push, and git lfs push (unchecked).
  • git -c, and the user's own equal-or-longer push rules for typed URLs.
  • Remotes the pane didn't see when it started (git -C other, or remotes added later).
  • Typed scp-like URLs whose host starts with punctuation.
  • Tools with their own credentials.
  • COPILOT_GITHUB_TOKEN is readable in the pane.
  • A role that sets out to get past the belt (env -i, its own helper on PATH).

Copilot's command sandbox stays the user's call: its policy lives in the user's settings.

Docs:

  • docs/plan.md: decision 55, fact 38, and a rewrite of risk 2.
  • The plan's Panes and Safety sections, decisions 9, 50, 51 and 52, and the Sources.
  • Fact 37 fixed: in the live check, the baton feature start script ran but failed with baton: command not found, since baton wasn't on the live pane's PATH.
  • Plan rows: guard-orchestrator is now Done (feat(cli): guard the orchestrator's pane #36), belt In review (feat(cli): belt the role panes' git pushes and gh #37).
  • README: copilot's login line. AGENTS.md: the cli tests' TestMain clears the GitHub tokens.

Checks

  • Tests. mise run check is green: lint 0 issues, no vulnerabilities, total coverage 99.3%, cli 99.8%. The one uncovered spot is waitForWork's old guard; the belt code is fully covered.
  • Probes. 55 mutants of belt.go, remotes.go and pane.go, all caught. Ten were rewritten after they failed to build.
    • Three mutants that make every role pane refuse used to deadlock TestPaneWaitsForTheRolesWork, because its write to the pane's pipe blocked once the pane had ended.
    • A new helper, stillWaiting, now fails the test cleanly instead.
  • Smoke (a). The built binary ran baton feature start on a tmux server of its own (-f /dev/null, its own TMUX_TMPDIR and HOME, env -i), with a stand-in copilot.
    • The lead's pane got GIT_CONFIG_COUNT 1→67, with the inherited setting kept. GH_TOKEN became COPILOT_GITHUB_TOKEN.
    • Pushes to origin (its pushurl cleared), and to typed https and scp-like URLs, failed with 128. Pushes to a local remote and to a typed path went through.
    • gh --version and gh auth status failed before starting.
    • The orchestrator's pane was not belted.
  • Smoke (b). One run with the real copilot, offline, approved by the user through nudge.
    • Setup: COPILOT_OFFLINE, a stand-in model on 127.0.0.1, a throwaway COPILOT_HOME and HOME.
    • Copilot's bash tool appended its three git settings after the belt's (count 70). The same push and gh results held.
    • Cost: 0 AI credits, no network, no GitHub login.
    • Afterwards, the real ~/.copilot and its Keychain item were unchanged.
  • No leftover tmux servers, test folders or processes.

A role's pane now runs its copilot with a second belt behind the guard:
settings in its environment, which every command and script there
inherits, so that git's pushes out of the machine and gh fail, even
where the guard can't see them (decision 55, fact 38, risk 2).

- git: after the GIT_CONFIG_COUNT settings that baton inherits, a
  pushInsteadOf rule per character that starts a URL that leaves the
  machine sends the push to a remote helper that doesn't exist, plus a
  rule per remote URL that leaves and a cleared pushurl per remote with
  one (an insteadOf rule before git 2.46). Local pushes, fetches,
  pulls and clones don't change.
- gh: GH_CONFIG_DIR under /dev/null, so every gh command fails before
  it starts and never reaches the Keychain. The pane goes without the
  GitHub tokens; copilot gets GH_TOKEN's or GITHUB_TOKEN's as
  COPILOT_GITHUB_TOKEN for its own login.
- repo.RemoteURLs and repo.ClearsPushURLs read the remotes' raw URLs
  and whether git clears pushurls.
- The orchestrator's pane goes without the belt (decision 54).

Docs: decision 55, fact 38, risk 2, the Panes and Safety sections,
decisions 9, 50, 51 and 52, fact 37's start script, rows
guard-orchestrator (Done) and belt (In review), README's login line,
AGENTS.md's test tokens.
@sonarqubecloud

sonarqubecloud Bot commented Nov 4, 2026

Copy link
Copy Markdown

@Bakhomious
Bakhomious merged commit 22ffb28 into main Nov 4, 2026
3 checks passed
@Bakhomious
Bakhomious deleted the step/belt branch November 4, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant