From a6eb6473287a9101599de20c43dce5848ad9eb3e Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 16 Oct 2026 21:55:37 -0700 Subject: [PATCH] v1.91.118.0 test: held-install recovery fixture no longer assumes the checkout has no local main (#3237) * test: held-install recovery fixture no longer assumes the checkout has no local main actions/checkout leaves push runs on a local main branch, so the fixture's clone already had main and 'checkout --orphan main' failed. Build the root commit on an orphan held-fixture branch and push HEAD:refs/heads/main; add a regression covering a source on main and a detached source with no branch. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * v1.91.118.0 test: held-install recovery fixture no longer assumes the checkout has no local main Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 10 ++++++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- test/held-install-recovery.test.ts | 28 +++++++++++++++++++++++----- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f83e5000..8711235b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [1.91.118.0] - 2026-10-17 + +**Free Tests is green on pushes to main again.** Since v1.91.106.0, every push to main failed `test/held-install-recovery.test.ts`, while pull request runs passed. + +### Fixed +- **The held-install recovery fixture no longer assumes the checkout has no `main` branch.** On a push, `actions/checkout` leaves the checkout on a local `main`, so the fixture's clone already had `main` and `git checkout --orphan main` failed with `a branch named 'main' already exists`. Pull request runs check out a detached merge ref and never hit it. The fixture now builds on its own orphan branch and pushes `HEAD:refs/heads/main`, and a new test builds it from a source on `main` and from a detached source with no branch. + +### If you… +- **…write a test fixture that clones this repository:** don't create a branch with a fixed name such as `main` in the clone. Push runs and pull request runs leave different local branches behind. + ## [1.91.117.0] - 2026-10-17 **Pregate understands bats repositories and says why a fingerprint failed, `gstack-doctor` lists rescued edits and pasted skill lists, oversized SKILL.md files can no longer grow, and hosts without plan mode drop the plan-mode preamble.** The last PR of the 2026-10-15 fix wave (`docs/designs/FIX_WAVE_2026_10_15.md`, PR 5), built after #3190. diff --git a/VERSION b/VERSION index cb81a8e33..7aa026bb8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.117.0 +1.91.118.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index ca4169130..0db45b15b 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.117.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.118.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index 4decba857..e10343092 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.91.117", + "version": "1.91.118", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/held-install-recovery.test.ts b/test/held-install-recovery.test.ts index f7250ad18..3604370e2 100644 --- a/test/held-install-recovery.test.ts +++ b/test/held-install-recovery.test.ts @@ -36,15 +36,16 @@ const CONFLICT_LINE = 'THROTTLE_SECONDS=3600 # 1 hour'; * install sits one commit behind it, at the same tree without * bin/gstack-ff-update (an install that predates it), with VERSION 1.91.101.0. */ -function makeFixture() { +function makeFixture(source = ROOT) { const base = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-held-'))); bases.push(base); const origin = path.join(base, 'origin.git'); const work = path.join(base, 'work'); - execFileSync('git', ['clone', '-q', '--shared', ROOT, work], { timeout: 60_000 }); + execFileSync('git', ['clone', '-q', '--shared', source, work], { timeout: 60_000 }); // A root commit of this tree: the checkout may be shallow, and a bare - // origin refuses a shallow push. - git(work, 'checkout', '-q', '--orphan', 'main'); + // origin refuses a shallow push. The clone already has a local main when + // the source is on main (actions/checkout runs `checkout -B main` on a push). + git(work, 'checkout', '-q', '--orphan', 'held-fixture'); fs.writeFileSync(path.join(work, 'VERSION'), '1.91.101.0\n'); fs.rmSync(path.join(work, 'bin', 'gstack-ff-update'), { force: true }); git(work, 'add', '-A'); @@ -59,7 +60,7 @@ function makeFixture() { git(work, 'commit', '-q', '-m', 'the fixed release'); const fixed = git(work, 'rev-parse', 'HEAD'); execFileSync('git', ['init', '-q', '--bare', '-b', 'main', origin], { timeout: 60_000 }); - git(work, 'push', '-q', origin, 'main'); + git(work, 'push', '-q', origin, 'HEAD:refs/heads/main'); return { base, origin, held, fixed }; } type Fx = ReturnType; @@ -126,6 +127,23 @@ describe.skipIf(process.platform === 'win32')('held-install recovery (Git Bash f expect(form('recovery-powershell')).toContain('d=$(mktemp -d)'); }); + for (const checkout of ['on main', 'detached with no branch'] as const) { + test(`the fixture builds from a source checkout ${checkout}`, () => { + const src = path.join(fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-held-src-'))), 'src'); + bases.push(path.dirname(src)); + execFileSync('git', ['clone', '-q', '--shared', ROOT, src], { timeout: 60_000 }); + if (checkout === 'on main') git(src, 'checkout', '-q', '-B', 'main'); + else { + git(src, 'checkout', '-q', '--detach'); + for (const b of git(src, 'for-each-ref', '--format=%(refname:short)', 'refs/heads').split('\n').filter(Boolean)) git(src, 'branch', '-q', '-D', b); + } + const fx = makeFixture(src); + expect(git(fx.origin, 'rev-parse', 'refs/heads/main')).toBe(fx.fixed); + expect(git(fx.origin, 'rev-parse', `${fx.fixed}^`)).toBe(fx.held); + expect(git(fx.origin, 'rev-list', '--max-parents=0', fx.fixed)).toBe(fx.held); + }, 60_000); + } + for (const state of ['clean', 'dirty', 'unmerged'] as const) { test(`a ${state} prefix-mode install reaches the fixed release; a planted symlink is never followed`, async () => { const fx = makeFixture();