Skip to content

mcpx testing: format.yaml -- auto-format after a green run; PR head vs main #1997

Closed

Description

@dezren39

Relocated from dezren39/nix#142 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).

Parent: #95

Summary

The notes ask for a separate format.yaml that auto-commits formatting to main after the formatters succeed and the test workflow is green. This issue works through what that actually requires (trigger, token, loop guard, branch protection, the race with newer commits) and finds a logical conflict with the check-mode formatters in the test matrix (#141). Plain conclusion: auto-committing to main is worse than formatting the PR head; the recommendation is a blocking format check on PRs plus an autofix push to the PR branch, and a main-side workflow that opens a PR rather than committing directly. If the direct-to-main version is still wanted, the safe way to build it is written out below.

Original notes

  • testing
    • separately, format.yaml which auto-commits to main when format has changes and successfully completes all formatters, receives a green test workflow

Current state

  • No formatting automation. The only bot push today is the vendorHash repair in mcpx.yml (:67-95 at 05c78b2), which commits to renovate/* / dependabot/* PR branches with GITHUB_TOKEN. Its comment states the repository's policy on rewriting branches (mcpx.yml:68-70): "A human changing dependencies should see the failure and fix it deliberately rather than have it rewritten under them." The step ran on a real Renovate PR for the first time in Phase 5: compaction for tag clears #184 (run 36693225089) but took the no-op path ("builds as-is; nothing to repair"), so the push-back itself has still never run.
  • main has no branch protection and no rulesets (.../branches/main/protection -> 404; .../rulesets -> []), so a direct push from Actions would succeed today, and would start failing the day protection is turned on.
  • Token behaviour (trigger docs): events caused by GITHUB_TOKEN do not start new workflow runs -- so a push to main with it is never tested. The exception is pull_request opened/synchronize/reopened, which now create runs in an approval-required state. A GitHub App installation token or PAT does trigger runs normally.
  • workflow_run (events docs): only fires for a workflow file on the default branch; types completed/requested/in_progress; exposes workflow_run.conclusion, head_branch, head_sha, event; chains at most three levels deep.

The conflict to resolve first

#141 puts formatters in check mode in the test matrix. If an unformatted commit reaches main, the fmt leg fails, the test workflow is red, and a format.yaml gated on "a green test workflow" never runs. Conversely, whenever the test workflow is green, formatting is already clean and there is nothing to commit. As written, the two notes cancel out. Ways out:

  1. Make the fmt leg non-blocking on main pushes (continue-on-error: ${{ github.event_name == 'push' }}) and have format.yaml check the individual job results through the API (workflow_run.conclusion is for the whole run). Works, but main can then be "green" while unformatted.
  2. Make formatting blocking on PRs, so main is always formatted by construction, and use format.yaml only for drift that does not come through a PR: a formatter version bump in flake.lock, a direct push. That is the recommended framing below.

Approaches

A -- auto-commit to main after a green run (the notes' design), least recommended

How to build it safely if chosen:

  • on: workflow_run: { workflows: [mcpx-test], types: [completed] }; if: conclusion == 'success' && event == 'push' && head_branch == 'main' (ignore cancelled, PR runs, and dispatches).
  • Check out workflow_run.head_sha; run the same pinned formatters (Bump dezren39/nix flake input to 3499ed3 — opencode now defaults to v2 #141: nix fmt); if git diff --quiet, exit.
  • Race: if main has moved past head_sha, skip (the newer commit's own run will trigger format.yaml again) rather than rebase -- rebasing a bot commit onto untested code is the worst of both.
  • Verify the diff is formatting-only before pushing: go build ./... and go vet ./..., deno check, nix eval of the package -- seconds, and it catches a formatter bug or a bad version bump.
  • Bound what the bot may write (from Phase 2: transfer with no successor drops grants #170): stage the explicit list from git diff --name-only, never git add -A (the repair step already stages one named file, mcpx.yml:93); abort on an unexpectedly large diff (a formatter version bump can legitimately reformat the whole tree -- a threshold such as 500 lines or 50 files, or any file outside a configured set, hands it to a human PR instead); re-run the check after committing and fail if it is still dirty, which catches two formatters fighting over one file.
  • Add workflow_dispatch beside workflow_run: workflow_run only fires for the workflow file on the default branch, so without it the workflow cannot be exercised before it merges (from Phase 2: transfer with no successor drops grants #170).
  • Loop guard: commit as github-actions[bot] with a marker ([mcpx-format]) and skip when the triggering head commit carries it; with GITHUB_TOKEN no run is triggered anyway. With an App token, the push triggers a test run, which triggers format.yaml, which finds no diff and stops -- "no diff" is the real guard, the marker is belt and braces.
  • Branch protection: when it is turned on, the pusher must be on the bypass list; that requires a GitHub App (PATs belong to a person and bypassing as a person is worse).

Why it is least recommended:

  • With GITHUB_TOKEN, the formatted commit on main is never tested. For gofmt that is nearly safe (it only rewrites layout); for deno fmt, shfmt, nixfmt after a version bump it is "probably fine", which is the wrong standard for main.
  • Every unformatted merge becomes two commits on main (the change, then "format"), permanently in history and in git blame.
  • It needs contents: write on the default branch in an automated job, and it fights branch protection.
  • It only ever fires if the conflict above is resolved by making main's format check non-blocking.

B -- open a formatting PR from main drift

Same trigger as A; instead of pushing to main, peter-evans/create-pull-request (or gh pr create) opens or updates a single branch bot/mcpx-format with the diff. The PR runs the full test workflow (with an App token; with GITHUB_TOKEN it lands in the approval-required state), and a human or auto-merge merges it. Tested, reviewable, protection-compatible, one extra PR when drift happens. Good as the main-side fallback.

C -- blocking check on PRs, autofix pushed to the PR head, B as the main fallback (recommended)

  • The fmt leg in the test workflow is blocking on PRs (check mode, prints the diff and the one command to fix: nix fmt; the diff also goes into $GITHUB_STEP_SUMMARY as a git apply-able patch, from Phase 2: transfer with no successor drops grants #170).
  • An autofix job (same workflow, after fmt fails, same-repo branches only -- forks cannot be pushed to) runs nix fmt and pushes a commit to the PR branch, exactly like the existing vendorHash repair. Merge it into one "bot push-back" job with the vendorHash repair so a bot PR gets one push, not two. Scope it the way the repair is scoped (changed in the 2026-09-30 pass, from Phase 2: transfer with no successor drops grants #170): always on bot branches (renovate/*, dependabot/* -- where a formatter bump lands, open question 3), and on a human's branch only when the author opts in (e.g. an autofix label). The repository has already written down that a human's branch is not rewritten under them (mcpx.yml:68-70); an unconditional autofix contradicts that and rewrites a branch mid-review, invalidating review comments and forcing a pull before the next push. If the user wants autofix on every PR anyway, that is a policy change to make explicitly, together with the repair step's comment.
  • Token: a GitHub App token, so the push triggers a normal (not approval-gated) run of the full workflow; the superseded run is cancelled by the PR concurrency group (Update Rust crate tokio to v1.38.2 [SECURITY] #135). Loop guard: no diff -> no push; and skip when the head commit author is the App.
  • format.yaml exists only for drift on main that did not come through a PR (formatter bump, direct push): approach B.
  • Result: every commit on main is formatted and tested; with squash merges the autofix commit leaves no trace on main.
  • Off-the-shelf alternative to a custom job: autofix.ci (a GitHub App, free for OSS) does the PR-head push with its own loop/fork handling; it requires trusting a third-party app with write access. Worth considering; decide with the App-token question.

Recommended because it keeps the notes' intent (nobody hand-formats; formatting never blocks a human for long) without an untested commit on main, and without overriding the branch-rewriting policy the repository already has.

Open questions

  1. Is the user OK replacing "auto-commit to main" with "autofix the PR head + a formatting PR for drift"? If not, build A with the safeguards above.
  2. GitHub App (new, owned by the user) vs autofix.ci vs a fine-grained PAT for the push-back? The same token would fix the vendorHash repair's approval-required runs (build(deps): bump build/buck/preludes/buck2-prelude from d956689 to 3cfc766 #49).
  3. Should Renovate/Dependabot formatter bumps (a new nixfmt in flake.lock) reformat inside the bump PR via the same autofix job? That is the cleanest place for format drift to land.
  4. Does the user merge by squash? (If merge commits are used, autofix commits appear in main's history.)
  5. A pre-commit hook (and which framework), or is nix fmt / just fmt plus the blocking check enough? A hook makes the blocking check nearly unreachable and format.yaml almost dead code -- which is fine; it is a backstop (from Phase 2: transfer with no successor drops grants #170).
  6. Bot identity: reuse mcpx-hash-bot (mcpx.yml:91-92) or a separate mcpx-format-bot so git log says which bot did what? Cosmetic; decide once (from Phase 2: transfer with no successor drops grants #170).
  7. Should the same push-back machinery regenerate derived artefacts CI checks (man page, OpenAPI, settings table)? That is Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96/⚡ Bolt: Optimize cursor color generation and position grouping #145's drift problem; build the mechanism once (from Phase 2: transfer with no successor drops grants #170).

Ease · Impact · Related

Ease M (needs a token decision) · Impact low-med (convenience; quality comes from the blocking check) · Depends on / related: #95, #141 (the formatters and their pinning), #135 (concurrency cancels superseded runs; the vendorHash push-back job this merges with), #49 (bot push-back for every package, token choice), #99 (agents run nix fmt before pushing; CI is the backstop).

Security/auth deferred; see tracker.

Acceptance criteria

  • format.yaml auto-formats only after a green run, distinguishes PR-head vs main behaviour as specified, and its CI run is green.
  • The work described in the Summary/Scope above is delivered via a branch + PR (no direct push to main); verification runs in GitHub Actions CI, not on the sandbox.
  • Evidence (CI links, timings, or probe output as applicable) is recorded on this issue before it is closed.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    History carried over from dezren39/nix#142 (verbatim, oldest first):


    @dezren39 · 2026-09-30T06:51:40Z

    Overlaps #170, written concurrently by a second worker on the same slice. Both are kept until reconciled; see the tracker (#95) for the differences and which one to keep.


    @dezren39 · 2026-09-30T07:21:02Z

    Integration pass. Placed in the DAG rooted at #181.


    @dezren39 · 2026-09-30T12:03:18Z

    Issues pass (2026-09-30, at 05c78b2).

    • Changed the recommendation's scope, from closed Phase 2: transfer with no successor drops grants #170. The repository already states its branch-rewriting policy in the repair step (mcpx.yml:68-70: "A human changing dependencies should see the failure and fix it deliberately rather than have it rewritten under them"). C's autofix is now always-on for bot branches and opt-in (label) for human PRs; an unconditional autofix would contradict that policy and should be an explicit policy change if wanted.
    • Carried from Phase 2: transfer with no successor drops grants #170: stage explicit paths only, abort on an unexpectedly large diff (formatter bumps), re-check idempotence after committing, add workflow_dispatch beside workflow_run, the step-summary patch; open questions on a pre-commit hook, bot identity, and regenerating derived artefacts.
    • Fact: the repair step ran on a real Renovate PR for the first time (Phase 5: compaction for tag clears #184, run 36693225089), on its no-op path; the push path is still unexercised.
  3. added
    enhancementNew feature or request
    queue/queuedHas a queued task in the Supabase agent queue
    kind/testTest coverage, test infrastructure, or test-only fixes
    priority/normalOrdinary priority (the default)
    effort/sSmall: expected under 2 hours of work
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 28, 2026
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    Intake review

    • Related: part of the mcpx testing relocation batch (parent/epic references in the body; re-home tracked at mcpx: re-home the open mcpx backlog from dezren39/nix, with this repo as the single source #309). Queued triage task is live in repo-health-code-pilot — this issue is not ownerless.
    • Live check: filed 2026-10-28 ~22:5xZ as a relocation; no open or merged PR implements it yet (checked against the open-PR list this run), and no duplicate open issue covers it.
    • Notes: body is well-scoped with evidence from the original filing; the acceptance criteria appended this pass are drawn from its own Summary/Scope.
    • Risks: historical issue references inside the body use dezren39/nix numbering — a worker must not chase those numbers in this repo. CI-first verification applies (GitHub Actions, never sandbox builds).
    • Suggested approach: triage task confirms the current-state claims on current main, then routes a scoped follow-up packet per the body's scope.

    🤖 Agent note · state: filed

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: repo-fleet-monitor run 2026-10-28 18:19 CDT
    • Found via: monitor reconciliation (intake enrichment, protocol §5)
    • Task: Intake enrichment — acceptance criteria appended, labels normalized (kind/test, priority/normal, effort/s, app/mcpx), meta/enriched applied.
    • Expectation: effort/s class per the appended acceptance criteria.
  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  6. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Triage verdict — scope corrected: the blocking-check half already exists, and a newer fleet issue now owns the autofix vehicle

    Triaged against developing-today/code main @ 4d157755 (2026-10-29). Queue task 2906 (kind triage, worker repo-worker-code, origin openchamber-scheduled-lane). No implementation here, as scoped.

    Verdict: not a duplicate, but do not build a mcpx-specific format.yaml. Formatting is already a blocking check, and the autofix workflow this issue recommends is now the subject of the fleet program (#2014 design, #2015 proof-of-concept). Keep this issue open, re-scoped to the mcpx-side input those issues need — and reconcile one real conflict, below.

    Current state (the body's "no formatting automation" has half moved)

    Body claim Today Evidence
    "No formatting automation." Half false. A treefmt check job exists and runs on both PRs and pushes to main, checking the changed files of the diff (git diff --name-only base...HEAD → nix run .#format-check). .github/workflows/nix-ci.yml:4-12,29-60
    "format.yaml … auto-commits" Absent. No format/fix/autofix workflow in .github/workflows/ at all. So a formatting diff reaching main today turns treefmt check red and nothing repairs it. ls .github/workflows (no format/fix)
    "No branch protection, no rulesets" Still true: /branches/main/protection is not set and rulesets returns [] — a workflow push to main would succeed today and break the day protection arrives. gh api …/branches/main/protection, …/rulesets → empty, 0
    "The conflict in #141: a green run can never need formatting" Live, and now sharper: the check runs on main pushes too (on.push.branches: [main]), so main can be red on formatting with nothing fixing it, and any fix gated on "a green test workflow" can still never fire. same

    A newer issue supersedes this one's implementation vehicle

    These come from the 2026-10-28 fleet directive and direct the opposite of this body's recommendation: they commit the formatter diff to main; this issue recommends formatting the PR head and opening a PR instead. That divergence must be settled in #2014 — and the sequencing consequence is the body's own option 1: if main is auto-formatted after the fact, the treefmt check on main pushes must not block (or must be ordered so it cannot cancel the repair), otherwise the repair job can never be reached. Two-way pointers added.

    Re-scope: this issue = the mcpx-side input to #2014/#2015 (what the check does today, the changed-files-only behaviour, the main-push red case, and the fact that PRs already get a blocking gate so the autofix only earns its keep for drift that bypasses a PR: flake.lock formatter bumps, direct pushes, merges that skip the hook). It should not grow a second workflow.

    Dedupe — no duplicates; neighbours named

    Issue Relation
    #1996 (nix#141) Formatters in the test matrix, pinned by the flake — the check half. This issue's autofix half depends on it and today that role is played by nix-ci.yml's treefmt job.
    #2014 / #2015 The fleet-wide autofix design and its PoC — the implementation vehicle. Not duplicates (fleet scope, different repo first), but they own the workflow this issue proposed.
    #1781 PR-tier slimming (what runs on PRs vs main) — governs whether the treefmt job belongs to the PR tier; already reflected in nix-ci.yml:66.
    #1901 (nix#95) Parent (this body's "Parent: #95" is nix numbering; #95 in this repo is a closed Dependabot bump).
    #1985 / #1995 Skeleton and budget: where a format leg's timing lands.
    #1780 Concurrency/cancel-in-progress policy the autofix must respect.

    Proposed next steps (small, mostly elsewhere)

    1. Here (S): record the mcpx-side facts as a comment addition for investigate: fleet-wide format auto-fix workflow — push token, loop guard, concurrency #2014 — changed-files-only check, main pushes already gated, no protection, no autofix, and pkgs/mcpx/** participates in the same treefmt job (the mcpx format-check is repo-wide, not a separate leg).
    2. In investigate: fleet-wide format auto-fix workflow — push token, loop guard, concurrency #2014 (decision): direct-to-main vs PR-head. If direct-to-main, the main-side treefmt check must stop being able to block the repair it needs (the body's option 1), or the repair must not be gated on the check's own result.
    3. In design+PoC: format auto-fix workflow in one repo, ready to replicate fleet-wide #2015 (implementation): one repo PoC; no mcpx-specific workflow file.
    4. Re-check after design+PoC: format auto-fix workflow in one repo, ready to replicate fleet-wide #2015 lands: if the fleet workflow covers developing-today/code, this issue closes as delivered by it, with the acceptance criteria (loop guard, no untested GITHUB_TOKEN push, main-side ordering) evidenced there rather than duplicated here.

    Corrections to carry

    • The body's mcpx.yml:67-95 vendorHash citation is stale (that step is now mcpx.yml:151-190ish and gated to bot branches); the policy quote it carries still stands.
    • #141→#1996, #142→#1997 (this), #143→#1998, #95→#1901, #130→#1973, #127/#166/#168 not relocated as issues (history only).

    Why no follow-up packet was enqueued: the implementation now belongs to #2014/#2015 (owned elsewhere), and repo-task/v1 has no implementation kind for the mcpx-side remainder — same pilot observation as #1983/#1990/#1994/#1944. Enqueueing a small_fix for step 2 would collide with #2014's decision.

    Labels left truthful: work/assigned, queue/claimed, status/in-progress removed.

    — worker repo-worker-code, openchamber scheduled lane, queue task 2906

  7. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · triage done (scope corrected)

  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Triage — task 7816 (queue repo-health-code-pilot, lane code-d)

    Verdict: superseded — closing with evidence. The auto-format function this issue designs was delivered repo-wide under Drew's own 2026-11-02 CI-policy redesign, with different mechanics than this issue recommended. No further work is owed on issue #1997.

    Evidence at current main (verified fresh 2026-11-03 via gh):

    • .github/ci-policy.md (10,757 bytes on main) sets the policy: formatting is mechanical and NEVER gates a merge; no treefmt/format context is in the required checks.
    • .github/workflows/auto-format.yml implements it: treefmt on a 15-minute schedule, formats files changed since the last successful pass, commits fixes directly to main as github-actions[bot], staging an explicit changed-file list (never git add -A); no diff means no commit. Workflow state: active; latest run 2026-11-03T09:47Z success; repo variable AUTO_FORMAT_LAST_SUCCESS_SHA advancing (updated 2026-11-03T09:48:30Z).
    • The pass is repo-wide treefmt, which covers pkgs/mcpx — the mcpx-scoped ask here is subsumed.

    How this issue's open questions were answered by that directive:

    • Q1 (auto-commit to main vs PR-head autofix): Drew chose direct-to-main in the scheduled design, sidestepping the green-run paradox by making formatting non-blocking by policy instead of gating a format workflow on a green test run.
    • Q2 (token): a fine-grained PAT (repo automation token, this repo only) — its pushes trigger downstream checks like a human push, answering the "untested commit on main" objection.
    • Q6 (bot identity): github-actions[bot], commit subject style(treefmt): auto-format on main.

    Stale premises in the issue body, corrected: main now HAS branch protection (the protection endpoint returns a config; the automation PAT is the sanctioned write path); the mcpx test matrix now lives in mcpx-test.yml. Separately, most mcpx workflows are currently disabled_manually — a fleet-known infra state, not part of this issue; auto-format.yml itself is active.

    The literal acceptance line "auto-formats only after a green run" was deliberately not adopted: the delivered design formats on schedule and the PAT push is checked afterwards like any human push. That deviation is Drew's recorded directive in the workflow header (2026-11-02), so this issue closes as superseded rather than escalated.

    Labels reconciled to terminal state (no queue/status/work labels remain).

  9. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workenhancementNew feature or requestkind/testTest coverage, test infrastructure, or test-only fixesmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first looktask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions