Skip to content

ORB-164: register git-guardrails on PowerShell and carry the external-interface and never-admin-merge rules in AGENTS.md - #442

Merged
thomasluizon merged 4 commits into
mainfrom
feature/orb-164-harness-repair-api-half
Jul 31, 2026
Merged

ORB-164: register git-guardrails on PowerShell and carry the external-interface and never-admin-merge rules in AGENTS.md#442
thomasluizon merged 4 commits into
mainfrom
feature/orb-164-harness-repair-api-half

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Harness-Lockstep-Peer: 665

ORB-164, the orbit-api half of the harness repair specification carried by ORB-163. This pull request lands only the three items that do not depend on the paired orbit-ui-mobile pull request. The remaining items stay for a later pass, because each one is bound to an edit that has not landed in #665 yet: deleting .github/workflows/claude-review.yml and the four twins that name it, the pr-review/rubric.md twin edit, the byte-exact second-opinion.mjs correction, and the pr-review/SKILL.md clause 6 and 7 corrections.

What changed

1. git-guardrails.mjs now runs on the PowerShell tool as well as Bash. .claude/settings.json registered its only PreToolUse hook on the matcher "Bash", so the identical git command issued through the PowerShell tool fired no hook at all and this repository's local branch protection was silently absent there. There are now two matchers, Bash and PowerShell, both invoking the same hook.

2. AGENTS.md carries the "Never assume an external interface" rule at line 48, with its review-rule counterpart as Code Review Rule 5 at line 97. orbit-ui-mobile PR #659 landed this rule in its own AGENTS.md, but codex workers launched into an orbit-api worktree read THIS repository's AGENTS.md, so the rule did not reach them. Adapted to this repository's externals (dotnet, NuGet) and its existing review-rule numbering rather than pasted.

3. AGENTS.md carries the never-admin-merge prohibition from specification section J3a, at line 33, alongside the existing branch-protection guardrail. It names all three shapes: gh pr merge --admin, a direct PUT /repos/{owner}/{repo}/pulls/{number}/merge, and the GraphQL mergePullRequest mutation. Naming both raw API calls is load bearing, because forbidding only the CLI flag leaves both API paths open. The one legitimate exception is stated: if a merge genuinely needs the admin override, the agent stops and asks Thomas to merge it himself.

The branch was also renamed from thomasluizon/orb-164-harness-repair-api-half to feature/orb-164-harness-repair-api-half, which is what CLAUDE.md requires.

Proof that the guard actually refuses

Observed live in this worktree against the final file state, through a headless session rooted at the worktree so this repository's .claude/settings.json is the project settings in effect. Both attempts were refused before the command reached git.

Through the PowerShell tool:

PreToolUse:PowerShell hook error: [node "$CLAUDE_PROJECT_DIR/.claude/hooks/git-guardrails.mjs"]: BLOCKED git command (Orbit git workflow):
  git push origin main

Direct or force push to main is forbidden (branch protection, squash-merge only). Open a PR.
The user has prevented this. See the "Git workflow" section of CLAUDE.md.

Through the Bash tool:

PreToolUse:Bash hook error: [node "$CLAUDE_PROJECT_DIR/.claude/hooks/git-guardrails.mjs"]: BLOCKED git command (Orbit git workflow):
  git push origin main

Direct or force push to main is forbidden (branch protection, squash-merge only). Open a PR.
The user has prevented this. See the "Git workflow" section of CLAUDE.md.

git-guardrails.mjs:24 reads input.tool_input.command. The refusal echoing git push origin main back verbatim is the confirmation that the PowerShell tool populates that exact field, so the hook needed no payload change. The hook exits 0 silently when command is not a string, so a missing field would have produced a silent pass rather than this block. Local main and origin/main were both at 319c774c when the attempts ran, so a hook miss would have been a no-op push rather than a real one.

Branch protection payload, recorded before the first edit

Specification section J2 requires this because no revert restores branch protection state. Read from GET /repos/thomasluizon/orbit-api/branches/main/protection on 2026-07-30, strict: true, 15 required contexts:

Unit Tests
Build
Dependency Review
OpenAPI Breaking-Change Gate
Dependency Scan
Guard Conventions
Guard Migrations
GitGuardian Security Checks
Analyze (csharp)
Analyze (javascript-typescript)
Analyze (actions)
SonarCloud Code Analysis
Dash Ban
Skill and Agent Frontmatter
Harness Lockstep

This pull request changes no branch protection setting and moves no guards.yml job name.

Harness Lockstep on this pull request, and why

The three files changed here are AGENTS.md, .claude/settings.json and .claude/hooks/git-guardrails.mjs. None of them is one of the six lockstep twins that orbit-ui-mobile/tools/check-lockstep.mjs:16-24 fixes at exactly six entries, and none of them is guards.yml. The Detect a lockstep change step at guards.yml:77-87 therefore evaluates to changed=false, every following step is skipped, and the job reports success without resolving the peer at all. So Harness Lockstep here is green because the twins were untouched, not because the pair is complete. It is not evidence that the twin edits are done, and the later pass that lands them will be the run that actually exercises the sibling checkout.

The Harness-Lockstep-Peer: 665 directive is on the first line regardless, because orbit-ui-mobile#665's mirror job requires the exact reciprocal line naming this pull request's number in this body. #665 must carry Harness-Lockstep-Peer: with this pull request's number or its own Harness Lockstep job fails.

Also fixed while in the file

git-guardrails.mjs carried two em dashes, one of them inside the message the hook prints to the user. tools/check-dashes.mjs skips .claude/ entirely, so the repository wide dash ban never saw them. Both are gone.

Testing

No product code and no new decision path in a tool, so no unit test. node tools/check-dashes.mjs --files AGENTS.md .claude/settings.json .claude/hooks/git-guardrails.mjs exits 0, node tools/check-dashes.mjs --check-baseline exits 0, node tools/check-frontmatter.mjs reports 8 files parsing, node --check .claude/hooks/git-guardrails.mjs exits 0, and .claude/settings.json parses as JSON. The lefthook dashes pre-commit step and the protect-main pre-push step both ran green.

Linear: ORB-164

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bojq5cLFZ87iBD8zhnoSWH

thomasluizon and others added 2 commits July 30, 2026 19:44
The only PreToolUse matcher named "Bash", so the identical git command issued
through the PowerShell tool fired no hook at all and git-guardrails.mjs, the
local branch protection this repository relies on, was silently absent there.

Verified live in this worktree, both refused after the change:
  PreToolUse:PowerShell hook error: BLOCKED git command (Orbit git workflow):
    git push origin main
  PreToolUse:Bash hook error: BLOCKED git command (Orbit git workflow):
    git push origin main

The refusal echoing the command back confirms the PowerShell tool populates
tool_input.command, the field the hook reads, so no payload change was needed.

Also removes the two em dashes the hook carried; tools/check-dashes.mjs skips
.claude/ so the ban was never enforced on them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bojq5cLFZ87iBD8zhnoSWH
…AGENTS.md

Codex workers launched into an orbit-api worktree read this repository's
AGENTS.md, so orbit-ui-mobile PR #659's "Never assume an external interface"
rule and its review-rule counterpart never reached them. Added here, adapted
to this repository's externals (dotnet, NuGet) and its review-rule numbering.

Adds the never-admin-merge prohibition from the harness repair specification
section J3a alongside the existing branch-protection guardrail: no
gh pr merge --admin, no direct PUT on the pulls merge endpoint, and no GraphQL
mergePullRequest mutation. Naming both raw API calls is the point; forbidding
only the CLI flag leaves the API paths open. If a merge genuinely needs the
admin override, the agent stops and asks Thomas to merge it himself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bojq5cLFZ87iBD8zhnoSWH

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0175eae63

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AGENTS.md
Comment on lines +33 to +35
- Never perform an admin merge, in any shape: no `gh pr merge --admin`, no direct
`PUT /repos/{owner}/{repo}/pulls/{number}/merge`, and no GraphQL `mergePullRequest`
mutation. Naming the two raw API calls is deliberate; forbidding only the CLI flag

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce the new admin-merge prohibition

When a worker invokes gh pr merge --admin or either raw API form, this prohibition remains advisory: the repository-wide search finds no enforcement, and git-guardrails.mjs immediately allows commands without the word git (a direct hook invocation with the gh command exits 0). Because an admin merge bypasses the protections that could reject it after execution, add a pre-execution gate covering all three listed forms rather than placing this rule among guardrails described as mechanically enforced.

AGENTS.md reference: AGENTS.md:L26-L29

Useful? React with 👍 / 👎.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #442 (thomasluizon/orbit-api)

Scope: PR #442AGENTS.md, .claude/settings.json, .claude/hooks/git-guardrails.mjs (ORB-164)
Recommendation: APPROVE

Summary

Documentation/tooling-only PR. It registers the existing git-guardrails.mjs PreToolUse hook on the PowerShell matcher (previously Bash-only, so the identical git command issued through PowerShell fired no guard at all), and adds two AGENTS.md sections: a never-admin-merge prohibition and a "never assume an external interface" verification rule adapted from the sibling orbit-ui-mobile repo. No src/, DTO, controller, or packages/shared surface is touched, so backend-hard-rules, security-reviewer, and contract-aligner gates never fire. The PR body is unusually well evidenced (live hook-trip transcripts for both Bash and PowerShell, check-dashes/check-frontmatter/node --check output, a recorded branch-protection payload). No Critical/High findings survived review.

Findings

Critical / High / Medium

None.

Low / Info

  • .claude/hooks/git-guardrails.mjs also removes the file's two pre-existing em dashes (file:74, a user-facing message; file:79, a comment). tools/check-dashes.mjs explicitly skips the whole .claude/ tree, so this was never CI-required — a voluntary fix in exactly the domain (git-workflow enforcement) that upholds the rule. No action needed.
  • The new "never assume an external interface" rule is dogfooded by this PR itself: the PowerShell-matcher claim is backed by a live transcript in the PR body showing the hook actually tripping through the PowerShell tool, not an assumption from --help/memory.

Subagents

Agent Verdict
security-reviewer N/A — gate did not fire (no src/ code changed)
contract-aligner N/A — gate did not fire (no DTO/route/packages/shared touched)

Validation

Check Result
Build (dotnet) N/A — skipped per CI adaptation; no .cs changes in this diff anyway
Tests (dotnet) N/A — same; PR body's own local proof (check-dashes, check-frontmatter, node --check, JSON parse, lefthook dashes/protect-main) is consistent with the diff

Deferred — N/A dimensions & files not verdicted

  • Dimensions 8, 9, 10 (DESIGN.md/AI-slop, web↔mobile parity, i18n) — N/A, frontend-only / no apps/* touched.
  • Dimension 11 (Contract drift + backward-compat) — N/A, no DTO/route/shared-type change; no candidates.
  • Dimension 13 (Backend hard rules) — N/A, no src/Orbit.* touched.
  • Dimension 14 (FEATURES.md parity) — N/A, no user-facing feature surface changed.
  • PR body claim that orbit-ui-mobile PR #659 landed the sibling rule — not independently verifiable, mobile repo not checked out in this job.
  • Whether main's branch protection has enforce_admins enabled — the new "never admin-merge" bullet sits under AGENTS.md's pre-existing framing that the guardrail list is "enforced by ... GitHub branch protection," but an admin-merge override bypasses branch protection by definition unless enforce_admins is specifically on. Not confirmable from this diff alone. Not raised as a ranked finding (no evidence either way), but worth a human check: if enforce_admins is false, either enable it or soften the bullet's implied technical backstop to "policy-only."

What's good

  • Fixes a real, previously-silent guard gap (PowerShell-issued git commands bypassed branch protection entirely).
  • Rigorous self-verification in the PR body, matching the standard its own new rule demands of future changes.
  • New AGENTS.md content is adapted to this repo's externals, not pasted verbatim, and correctly cross-numbered.
  • Correctly scoped: explicitly excludes items still blocked on the unlanded orbit-ui-mobile peer PR rather than landing a partial lockstep pair.

Recommendation

Approve. Optional non-blocking follow-up: confirm enforce_admins: true on main's branch protection so the new never-admin-merge bullet has the technical backstop its section's framing implies.

Branch protection on main requires three code scanning contexts: "Analyze
(csharp)", "Analyze (javascript-typescript)" and "Analyze (actions)". They
were produced by CodeQL default setup, which does not run on Dependabot pull
requests (github/codeql-action issue 2858). The three contexts therefore never
reported on a Dependabot head, and every Dependabot pull request sat
permanently BLOCKED waiting on checks that could not arrive.

Default setup is now disabled on the repository, and this workflow takes over.
An advanced-setup workflow is an ordinary workflow, so it triggers on a
Dependabot head like every other job here, and its job-level permissions block
raises the read-only Dependabot GITHUB_TOKEN to security-events: write.

The matrix job name "Analyze (${{ matrix.language }})" interpolates to exactly
the three required context names, so branch protection needs no change. C# uses
build-mode manual with the same dotnet restore plus dotnet build --no-restore
that API Tests already proves, because this repository generates code at build
time; javascript-typescript and actions use build-mode none. github/codeql-action
is pinned by SHA to tag v4.37.4, matching the repository convention for
non-actions/* actions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bojq5cLFZ87iBD8zhnoSWH

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #442

Scope: Diff since the last review (commit 988e04c1), which added .github/workflows/codeql.yml. Earlier commits (.claude/hooks/git-guardrails.mjs, .claude/settings.json, AGENTS.md) were already reviewed and approved in a prior review at commit c0175eae and are not re-flagged here.

Recommendation: APPROVE

Summary

This commit adds an 87-line CodeQL "advanced setup" workflow replacing GitHub's default CodeQL setup. The motivation — default setup doesn't run on Dependabot-authored PRs, permanently blocking the three required status-check contexts — is a real, known codeql-action limitation. The workflow reproduces the three required check names (Analyze (csharp), Analyze (javascript-typescript), Analyze (actions)) via matrix-job-name interpolation, uses least-privilege job-level permissions (security-events: write, actions: read, contents: read), SHA-pins github/codeql-action, and reuses this repo's proven dotnet restore && dotnet build --no-restore build steps from test.yml for the C# manual-build-mode leg. No src/ or product code is touched, so backend hard-rules, security-reviewer, and contract-aligner gates don't apply.

Findings

Critical / High

None.

Medium

None.

Low / Info

  • The commit message asserts CodeQL default setup is now disabled on the repository — this is an external GitHub repo-setting change not visible in the diff, and not independently verifiable from this CI job (the code-scanning/default-setup API check was blocked by sandbox permissions). If that toggle wasn't actually flipped, advanced-setup SARIF uploads would be rejected while default setup is still on. Not blocking — worth a quick human glance at Settings → Code security.
  • actions/checkout@v7 / actions/setup-dotnet@v6 are tag-pinned rather than SHA-pinned in the new workflow, consistent with every other workflow in this repo. github/codeql-action is correctly SHA-pinned, matching the repo's convention for third-party actions.
  • No stale references to CodeQL default setup remain elsewhere in the repo that would need updating.

What's good

  • Correctly diagnoses and fixes a real, previously-blocking CI gap (Dependabot PRs stuck on default-setup contexts that never report).
  • Job-name interpolation is explicitly called out as load-bearing for branch protection.
  • Reuses this repo's own proven C# build steps rather than inventing new ones.
  • Permissions scoped tightly at workflow and job level; SHA-pinning matches repo convention.

Validation

Build/Test: N/A — skipped per CI adaptation (separate required checks handle this); no .cs changes in this diff.

Deferred / not verifiable in CI

  • Dimensions covering frontend/mobile parity, contract drift, and FEATURES.md: N/A — no product code, DTOs, or shared types touched.
  • CodeQL default-setup-disabled claim: not independently verifiable from this job's gh api access; flagged as Info above.

"SonarCloud Code Analysis" is a required context on main, and the SonarQubeCloud
app only posts it once an analysis is uploaded. The scanner job was skipped on
Dependabot heads because SONAR_TOKEN did not exist in the repository's Dependabot
secret store, so the required context never reported and every Dependabot pull
request stayed BLOCKED. SONAR_TOKEN is now present in that store, so the guard
has nothing left to protect and is removed. The job already reads the secret as
secrets.SONAR_TOKEN, which is the reference a Dependabot-triggered run resolves
against the Dependabot store.

claude-review.yml identified Dependabot with github.actor, which is whoever
triggered the run, not the pull request author. A human re-run of a Dependabot
run would therefore let the Claude review step through with an empty
CLAUDE_CODE_OAUTH_TOKEN, since that secret lives only in the Actions store. It
now checks the author, matching dependabot-auto-merge.yml. That step stays
skipped for Dependabot on purpose; "review" is not a required context.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bojq5cLFZ87iBD8zhnoSWH
@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit 3e20b92 into main Jul 31, 2026
23 checks passed
@thomasluizon
thomasluizon deleted the feature/orb-164-harness-repair-api-half branch July 31, 2026 00:37
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