From f396bd5ca8104951b35ad0befdf0db9588417513 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 1 Nov 2026 21:30:48 +0000 Subject: [PATCH] fix(windows): gstack-ship-receipt starts gstack-session-kind through Git Bash (#3541) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-ship-receipt | 3 ++- changelog.d/gsta208-ship-receipt-session-kind.md | 7 +++++++ test/gstack-ship-receipt.test.ts | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 changelog.d/gsta208-ship-receipt-session-kind.md diff --git a/bin/gstack-ship-receipt b/bin/gstack-ship-receipt index b2d902b65..3de38d032 100755 --- a/bin/gstack-ship-receipt +++ b/bin/gstack-ship-receipt @@ -37,6 +37,7 @@ import * as path from 'node:path'; import { EXIT, exitTableHelp, type ShipReceipt } from '../lib/headless-artifacts'; import { resultLine, resultLineWithDocs } from '../lib/result-codes'; import { versionAtRef } from '../lib/restamp'; +import { runScriptSync } from '../lib/script-invocation'; import { readPregateFile, verifyPregate } from '../lib/pregate/index'; import { isUnavailable, loadPolicyFromBase, matchesAny, policyUnavailableLine, resolveBaseBranch, runningGstackVersion, stampFilesInTree, validatePolicy, type ShipPolicy } from '../lib/ship-policy'; import { approvalForReceipt } from '../lib/approval-citations'; @@ -179,7 +180,7 @@ function cmdWrite(rest: string[], json: boolean): number { const tree = receiptFile ? JSON.parse(fs.readFileSync(receiptFile, 'utf8')) : null; let sessionKind = flag(rest, '--session-kind') ?? process.env.GSTACK_SESSION_KIND; if (!sessionKind) { - const r = spawnSync(path.join(path.dirname(Bun.fileURLToPath(import.meta.url)), 'gstack-session-kind'), [], { encoding: 'utf8', timeout: 15_000 }); + const r = runScriptSync(path.join(path.dirname(Bun.fileURLToPath(import.meta.url)), 'gstack-session-kind'), [], { encoding: 'utf8', timeout: 15_000 }); sessionKind = r.status === 0 ? r.stdout.trim() || undefined : undefined; } let pregate: PregateField | undefined; diff --git a/changelog.d/gsta208-ship-receipt-session-kind.md b/changelog.d/gsta208-ship-receipt-session-kind.md new file mode 100644 index 000000000..2de4746d9 --- /dev/null +++ b/changelog.d/gsta208-ship-receipt-session-kind.md @@ -0,0 +1,7 @@ +**`gstack-ship-receipt write` records the session kind on Windows.** Without `--session-kind` or `GSTACK_SESSION_KIND`, it asks the `gstack-session-kind` bash helper. It used to spawn that helper by path, which never starts on Windows, so every /ship receipt there carried no `session_kind`. It now starts the helper through `lib/script-invocation.ts`, which uses Git Bash on Windows. This is the /ship half of #3541. + +### Fixed +- **`bin/gstack-ship-receipt`** starts `gstack-session-kind` with `runScriptSync`. + +### Tests +- `test/gstack-ship-receipt.test.ts`: a receipt written without a session kind takes it from `gstack-session-kind`. diff --git a/test/gstack-ship-receipt.test.ts b/test/gstack-ship-receipt.test.ts index 7e0eb68c8..9eddd5710 100644 --- a/test/gstack-ship-receipt.test.ts +++ b/test/gstack-ship-receipt.test.ts @@ -72,6 +72,21 @@ describe('gstack-ship-receipt write', () => { expect(rewritten).toContain('## Summary'); }); + test('without --session-kind or GSTACK_SESSION_KIND, session_kind comes from gstack-session-kind (#3541)', () => { + const repo = fixture(); + makePr(repo, 'pr-a', { expects: '1.2.4.0' }); + expect(restamp(repo, ['--version', '1.2.4.0']).status).toBe(EXIT.ok); + repo.git('commit', '-qam', 'stamp'); + const body = path.join(repo.root, 'body.md'); + fs.writeFileSync(body, '## Summary\n\nProse.\n'); + const env = { GSTACK_SESSION_KIND: '' }; + const kind = runBin(repo, 'gstack-session-kind', [], env); + expect(kind.status, kind.err).toBe(0); + const w = runBin(repo, 'gstack-ship-receipt', ['write', '--pr', '44', '--base', 'main', '--body', body, '--json'], env); + expect(w.status, w.err).toBe(EXIT.ok); + expect(JSON.parse(w.out).session_kind).toBe(kind.out.trim()); + }); + test('refuses an invalid receipt before writing (RECEIPT_INVALID) and a non-integer --pr', () => { const repo = fixture(); const body = path.join(repo.root, 'body.md');