Skip to content

fix(qa): make the QA Session publish path safe on a shared branch - #808

Merged
FenrysUnchained merged 2 commits into
bleeding-edgefrom
claude/qa-backlog-7mvlsr
Aug 26, 2026
Merged

fix(qa): make the QA Session publish path safe on a shared branch#808
FenrysUnchained merged 2 commits into
bleeding-edgefrom
claude/qa-backlog-7mvlsr

Conversation

@FenrysUnchained

Copy link
Copy Markdown
Contributor

Follow-up to #799. Two fixes to the QA Session window's publish step, both on the path a tester takes from bleeding-edge — which is the default path now that #799 has merged, and which had never actually run: all testing before this was on a working branch, which takes a different branch of the code.

Found while re-reading that path before walking a first-time user through the tool.

1. -u on a refspec push hijacked the tester's upstream

The protected-branch path pushes to qa/results-<session> rather than checking anything out. That push carried -u.

With a refspec, -u sets the upstream of the source ref — the tester's current branch — not the ref being created. Reproduced in a scratch repo:

$ git push -u origin HEAD:refs/heads/qa/results-demo
branch 'bleeding-edge' set up to track 'origin/qa/results-demo'

So a tester who pressed Submit on bleeding-edge had their local bleeding-edge silently re-pointed at a QA results branch, and their next Pull origin in GitHub Desktop would have pulled that branch into the build they were testing. The tool reported success while doing it.

Dropping -u fixes it and still publishes the ref (verified in the same scratch repo: branch created, upstream left on origin/bleeding-edge). Nothing local tracks the QA ref and the tester never checks it out, so it needs no upstream.

2. Publishing no longer moves anything the tester owns

The same path still made a local commit before pushing it, which left bleeding-edge one commit ahead of origin forever — with a Push button in GitHub Desktop that either errors or, if the branch isn't protected server-side, lands a QA results commit directly on bleeding-edge.

No local commit is made at all now. The commit object is assembled with plumbing and pushed directly:

read-tree HEAD  →  add PATHS  →  write-tree  →  commit-tree  →  push

against a throwaway index (GIT_INDEX_FILE), which is also what keeps it scoped — whatever else the tester has dirty or staged is invisible to it. HEAD, the branch, the real index and the working tree are all untouched.

refs/qa-published/<stem> records what was last published for a session and parents the next publish, so a second submit fast-forwards the QA branch instead of being rejected as unrelated histories. It also keeps that commit object reachable against gc, and sits outside refs/heads so it never appears as a branch.

Shared infra

FrogletGit gains RunInWithEnv / RunWithEnv. Environment variables are the only way to reach GIT_INDEX_FILE — it has no command-line equivalent — and staging into a throwaway index is what lets any tool build a scoped commit without disturbing the human's index, branch or working tree.

Pure refactor: RunIn now delegates with a null env, so every existing caller (BugLedgerPublisher ×4) is byte-identical in behaviour.


Verification status

Prototyped against real git before any C# was written, checking the things that actually bite:

HEAD unmoved ✅ local == origin after publishing
upstream intact ✅ still origin/bleeding-edge
branch not ahead ## bleeding-edge...origin/bleeding-edge
scoped ✅ an unrelated dirty file was not in the pushed tree
repeat submit ✅ second publish fast-forwards the QA branch

Compiled for real (Roslyn, exit 0) — both QASessionWindow.cs and, because this touches shared infra, FrogletGit.cs itself against the real System.Diagnostics.Process ref assembly, so the process plumbing is type-checked rather than stubbed.

65 Python self-test checks green (30 session / 18 submit / 17 apply); check_conditional_compilation.py clean.

NOT verified — needs a human at a Unity install: the end-to-end press of Submit from bleeding-edge. The git sequence is proven, and the C# that drives it compiles, but no one has run the two together through the editor. That walkthrough is imminent and this PR is the version it should be run against.

If it does misbehave, the failure is loud and safe: the dialog falls back to naming the two files to commit by hand, and the results are already saved in the session file regardless.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 2 commits August 26, 2026 18:40
…tream

The QA window's publish step takes a different path on a protected branch
(bleeding-edge): rather than checking anything out, it pushes the commit
straight to a qa/results-<session> ref. That push carried -u.

With a REFSPEC, -u sets the upstream of the SOURCE ref -- which is the
tester's current branch, not the ref being created. Reproduced in a sandbox:

    git push -u origin HEAD:refs/heads/qa/results-demo
    branch 'bleeding-edge' set up to track 'origin/qa/results-demo'

So a tester who pressed Submit on bleeding-edge had their local bleeding-edge
silently re-pointed at a QA results branch, and their next Pull in GitHub
Desktop would have pulled that branch into the build they were testing. The
tool would have reported success while doing it.

Dropping -u fixes it and still publishes the ref (verified in the same
sandbox: branch created, upstream left on origin/bleeding-edge). Nothing local
tracks the QA ref and the tester never checks it out, so it needs no upstream
at all.

This path had never run: all testing so far was on a working branch, which
takes the ordinary `push -u origin <branch>` path where -u is correct. It
became the DEFAULT path the moment this work merged to bleeding-edge, since a
tester now starts there.

Known and unchanged: the commit is still made on the local protected branch
before being pushed to the QA ref, so the tester's local bleeding-edge sits
one commit ahead of origin afterwards. Harmless (the data is on the QA branch,
and it resolves when that merges) but untidy; building the commit with
commit-tree so the local branch never moves is the follow-up.

C# real-compiled (Roslyn + stubs, exit 0); 65 python selftest checks green;
conditional-compilation gate clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsUBppM6FtPcfyiZSM9Eun
…ster owns

Follow-up to the -u fix. Publishing from a protected branch still made a LOCAL
commit before pushing it to the QA ref, which left the tester's bleeding-edge
one commit ahead of origin forever -- with a Push button in GitHub Desktop that
either errors or, if the branch is not protected server-side, lands a QA results
commit straight on bleeding-edge.

No local commit is made at all now. The commit object is assembled with plumbing
and pushed directly:

    read-tree HEAD  ->  add PATHS  ->  write-tree  ->  commit-tree  ->  push

against a THROWAWAY index (GIT_INDEX_FILE), which is also what keeps it scoped --
whatever else the tester has dirty or staged is invisible to it. HEAD, the branch,
the real index and the working tree are all untouched.

refs/qa-published/<stem> records what was last published for this session and
parents the next publish, so a second submit FAST-FORWARDS the QA branch instead
of being rejected as unrelated histories. It also keeps that commit object
reachable against gc, and sits outside refs/heads so it never appears as a branch.

Prototyped against real git before any C# was written, and the prototype checks
the things that actually bite: HEAD unmoved, upstream still origin/bleeding-edge,
branch not ahead, an unrelated dirty file NOT swept into the pushed tree, and a
second submit fast-forwarding cleanly.

FrogletGit gains RunInWithEnv/RunWithEnv (env vars are the only way to reach
GIT_INDEX_FILE -- it has no command-line equivalent). Pure refactor: RunIn now
delegates with a null env, so every existing caller is byte-identical in
behaviour. Both FrogletGit.cs and QASessionWindow.cs were really compiled
(Roslyn, exit 0) -- the shared-infra file against the REAL
System.Diagnostics.Process ref assembly, so the process plumbing is type-checked
rather than stubbed.

65 python selftest checks green; conditional-compilation gate clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsUBppM6FtPcfyiZSM9Eun
@FenrysUnchained
FenrysUnchained merged commit 31210fa into bleeding-edge Aug 26, 2026
2 of 3 checks passed
FenrysUnchained pushed a commit that referenced this pull request Aug 26, 2026
…he job

The job was cancelled MID-CHECKOUT three times on 2026-08-26, across two
unrelated branches (PRs #808 and the arcade-launch-screen branch, twice). Every
check step showed "skipped" -- the checker never ran a line -- so it surfaced as
a red X that said nothing at all about the code.

timeout-minutes: 5 was sized for the checks, which take seconds. In practice it
bounds the CHECKOUT, which the job does not control: Assets/ is ~1.4 GB and
almost none of it is in LFS (two filter=lfs rules in .gitattributes), so even at
fetch-depth 1 this job pulls ~1.4 GB to read ~5 MB of .cs and shader sources.
Usually that lands inside a minute -- healthy runs finish in 57-90s -- and when
throughput dips it does not.

Raising the ceiling does not make the job slower; it only stops a slow clone
being reported as a failing check. At 20 minutes a run that actually reaches the
cap is a real fault worth failing on.

The narrower fix is to stop fetching what this job never reads (filter:
blob:none + sparse-checkout for *.cs / *.shadergraph / *.shader / *.hlsl /
Tools). Deliberately not done here: it needs its own validation pass. Verified
in passing that it would at least fail SAFELY -- with a shader file absent the
wiring checks raise FileNotFoundError rather than passing -- so a wrong pattern
breaks CI visibly instead of going green on an unchecked tree. The root cause
under both is that the binary assets are not in LFS, which slows every job here
including the Unity build.

Validated: the workflow still parses (all three jobs), conditional-compilation's
steps are unchanged, and the functional diff is one line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsUBppM6FtPcfyiZSM9Eun
Soncub pushed a commit that referenced this pull request Aug 26, 2026
…stered)

- QA-MAELSTROM-POOL PASS -> ARCHIVE (all seven modes present, clean load in/out, scoring ok)
- scan 3bbe4f7 -> c719533 (PRs #583-#809), 221 commits / ~25 PRs
- new P0: QA-SPARROW-SALVO-MODE (#790, new Sparrow party game 'Salvo')
- new P1: QA-SPARROW-VISUAL-REWORK (#801/#794/#791/#787/#786/#784/#798),
  QA-UI-ABILITY-ICON-SYSTEM (#803/#804), QA-UI-THEME (#795),
  QA-ECOLOGY-TIME-BREEDING (#774), QA-SCARAB-BALL-FIXES (#807/#780/#773)
- folded: prism-shield color/pop (#771/#778/#785), worm colony (#793), quadfish/AI/camera fixes,
  the QA workflow itself merged upstream (#800/#808), CI (#809)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3Nu7PRozvXnV4W41rEqNq
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.

2 participants