From dcc1c51bfcfacc16efe0bdbc352970db5b84c9c2 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 1 Nov 2026 00:56:05 -0700 Subject: [PATCH] v1.92.19.0 Skip the Aside probe off macOS so Linux and Windows render through gstack's own browser (#2864) (#3734) * aside-render: probeAside answers NEEDS_ASIDE off macOS without spawning (#2864) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: off-macOS probe detail no longer claims gstack's browser is in use 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 | 9 +++++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- lib/aside-render.ts | 7 +++++-- make-pdf/test/cli-exit-codes.test.ts | 14 +++++++++++++- package.json | 2 +- test/aside-render.test.ts | 23 ++++++++++++++++++++--- 7 files changed, 50 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8bf19dcc..ff7fd279b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.92.19.0] - 2026-11-01 + +**On Linux and Windows the renderer goes straight to gstack's own browser instead of asking for Aside (#2864).** `probeAside()` in `lib/aside-render.ts`, used by /make-pdf, /diagram and `gstack-render`, used to run `aside --version` on any platform. When that failed it told the user to install a browser that only ships for macOS. Off macOS it now answers `NEEDS_ASIDE` with "Aside is macOS-only (this is linux)", without starting a process, and the renderer moves on to gstack's own browser. macOS behavior and `GSTACK_SKIP_ASIDE=1` are unchanged. + +### Changed +- **`probeAside(timeoutMs, platform)`** takes the platform, which defaults to `process.platform`. The fake-CLI probe tests pass `darwin`, so they still run on Linux CI. + +Thanks to @furkankoykiran for the report. The bash readiness block inside browsing skills already prints `NEEDS_ASIDE: Linux` and is unchanged. + ## [1.92.18.0] - 2026-11-01 - `install.sh --project` (through `gstack-capy-install`) resolves the directory it names to its git toplevel, which is where pregate admission looks for the install verdict. `--project /` now writes, and reads pins from, the same place as `--project `, so a Bun pin declared only in a subpackage no longer applies. A value that isn't a directory, such as a repo slug or a directory this shell can't enter, is a usage error that asks for the checkout's path, and it stops before anything is cloned or installed. Before this change, a slug or a subdirectory keyed the verdict somewhere admission never looks, so a later pregate admission check read a stale verdict (GST-743). diff --git a/VERSION b/VERSION index 96029e617..42b0e21c8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.92.18.0 +1.92.19.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index b5fdacc28..02a0cf306 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.92.18.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.92.19.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/lib/aside-render.ts b/lib/aside-render.ts index 3ffde814a..f5e2b6f1a 100644 --- a/lib/aside-render.ts +++ b/lib/aside-render.ts @@ -76,11 +76,14 @@ export type AsideProbe = | { ok: true; version: string } | { ok: false; reason: 'NEEDS_ASIDE' | 'ASIDE_NOT_RUNNING'; detail: string }; -/** Same probe the skills run in BROWSER SETUP: binary present, app answering. */ -export function probeAside(timeoutMs = 30_000): AsideProbe { +/** Same probe the skills run in BROWSER SETUP: binary present, app answering. Aside ships only for macOS, so any other platform answers NEEDS_ASIDE without spawning. */ +export function probeAside(timeoutMs = 30_000, platform: NodeJS.Platform = process.platform): AsideProbe { if (process.env.GSTACK_SKIP_ASIDE === '1') { return { ok: false, reason: 'NEEDS_ASIDE', detail: 'GSTACK_SKIP_ASIDE=1 — Aside skipped by request' }; } + if (platform !== 'darwin') { + return { ok: false, reason: 'NEEDS_ASIDE', detail: `Aside is macOS-only (this is ${platform})` }; + } const which = spawnSync('aside', ['--version'], { encoding: 'utf8', timeout: 10_000 }); if (which.error) { return { ok: false, reason: 'NEEDS_ASIDE', detail: 'the `aside` CLI is not on PATH — install the Aside browser (macOS 15+) from aside.com' }; diff --git a/make-pdf/test/cli-exit-codes.test.ts b/make-pdf/test/cli-exit-codes.test.ts index 2eb68ffab..b9160c79f 100644 --- a/make-pdf/test/cli-exit-codes.test.ts +++ b/make-pdf/test/cli-exit-codes.test.ts @@ -36,6 +36,7 @@ const ROOT = path.resolve(import.meta.dir, "..", ".."); const CLI = path.join(ROOT, "make-pdf", "src", "cli.ts"); const TYPES = path.join(ROOT, "make-pdf", "src", "types.ts"); const isWin = process.platform === "win32"; +const isMac = process.platform === "darwin"; let tmp: string; /** PATH for children: nothing on it (no `aside`, no `browse`, no `pdftotext`). */ @@ -316,7 +317,7 @@ describe.skipIf(isWin)("cli.ts through fake browsers (GSTACK_BROWSE_BIN + a scru expect(fs.existsSync(out)).toBe(false); }); - test("Aside answers the probe but cannot run the script: the PDF comes from gstack's browser, stderr says so, exit 0", () => { + test.skipIf(!isMac)("Aside answers the probe but cannot run the script: the PDF comes from gstack's browser, stderr says so, exit 0", () => { const out = path.join(tmp, "mid-run", "out.pdf"); const r = runCli(["generate", inputMd, out, "--quiet"], { env: childEnv({ PATH: fakeAsideBin, GSTACK_SKIP_ASIDE: undefined, GSTACK_BROWSE_BIN: browseOk }), @@ -326,4 +327,15 @@ describe.skipIf(isWin)("cli.ts through fake browsers (GSTACK_BROWSE_BIN + a scru expect(r.stderr).toContain("Aside was unavailable mid-run; the PDF was rendered through gstack's own browser."); expect(fs.readFileSync(out, "utf8")).toBe("%PDF-1.4 fake\n"); }); + + test.skipIf(isMac)("off macOS an `aside` on PATH is never asked: the PDF comes straight from gstack's browser, --quiet keeps stderr empty, exit 0 (#2864)", () => { + const out = path.join(tmp, "off-mac", "out.pdf"); + const r = runCli(["generate", inputMd, out, "--quiet"], { + env: childEnv({ PATH: fakeAsideBin, GSTACK_SKIP_ASIDE: undefined, GSTACK_BROWSE_BIN: browseOk }), + }); + expect(r.code).toBe(ExitCode.Success); + expect(r.stdout).toBe(`${out}\n`); + expect(r.stderr).toBe(""); + expect(fs.readFileSync(out, "utf8")).toBe("%PDF-1.4 fake\n"); + }); }); diff --git a/package.json b/package.json index 5aa2064c5..cf7198c84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.92.18", + "version": "1.92.19", "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/aside-render.test.ts b/test/aside-render.test.ts index 5ecc3b383..67dc8bfd4 100644 --- a/test/aside-render.test.ts +++ b/test/aside-render.test.ts @@ -350,7 +350,7 @@ function writeDriver(dir: string): string { fs.writeFileSync(driver, `const M = await import(${JSON.stringify(LIB)}); const job = JSON.parse(process.argv[2]); let out; -if (job.fn === 'probeAside') out = M.probeAside(job.timeoutMs); +if (job.fn === 'probeAside') out = M.probeAside(job.timeoutMs, job.platform); else if (job.fn === 'renderWithAside') out = await M.renderWithAside(job.spec); else if (job.fn === 'render') { if (job.primeAside) M.pickEngine(true, { probe: () => ({ ok: true, version: 'fake-aside' }) }); @@ -383,9 +383,9 @@ describe.skipIf(!HERMETIC)('aside-render: probeAside classifies a fake CLI the w let driver: string; beforeAll(() => { fs.mkdirSync(bin); driver = writeDriver(tmp); }); afterAll(() => fs.rmSync(tmp, { recursive: true, force: true })); - const probe = (env?: Record): AsideProbe => { + const probe = (env?: Record, platform = 'darwin'): AsideProbe => { fs.rmSync(log, { force: true }); - return runDriver(driver, { fn: 'probeAside', timeoutMs: 5_000 }, { binDir: bin, env }); + return runDriver(driver, { fn: 'probeAside', timeoutMs: 5_000, platform }, { binDir: bin, env }); }; test('no `aside` on PATH → NEEDS_ASIDE (install it), never "not running"', () => { @@ -442,6 +442,23 @@ describe.skipIf(!HERMETIC)('aside-render: probeAside classifies a fake CLI the w expect(r.detail).toContain('GSTACK_SKIP_ASIDE=1'); expect(fs.existsSync(log)).toBe(false); }); + + test.each(['linux', 'win32'])('on %s → NEEDS_ASIDE naming the platform, and the CLI is never invoked, even when one is on PATH (#2864)', platform => { + writeFakeAside(bin, { repl: 'echo "ASIDE_READY /x"', log }); + const r = probe(undefined, platform); + expect(r.ok).toBe(false); + if (r.ok) return; + expect(r.reason).toBe('NEEDS_ASIDE'); + expect(r.detail).toBe(`Aside is macOS-only (this is ${platform})`); + expect(fs.existsSync(log)).toBe(false); + }); + + test('GSTACK_SKIP_ASIDE=1 keeps precedence over the platform check', () => { + const r = probe({ GSTACK_SKIP_ASIDE: '1' }, 'linux'); + expect(r.ok).toBe(false); + if (r.ok) return; + expect(r.detail).toContain('GSTACK_SKIP_ASIDE=1'); + }); }); describe.skipIf(!HERMETIC)('aside-render: serveDir — loopback server contract (nonce, containment, no listings)', () => {