fix(aside): the readiness probe can never detect Aside under zsh - #2824
AntonioVitalic wants to merge 1 commit into
Conversation
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
…ct Aside
The BROWSER SETUP probe stored its deadline wrapper as a string and ran it as
a bare word:
_T="gtimeout 30" # or "timeout 30", or "perl -e alarm(shift);exec(@argv) 30"
$_T aside repl ...
zsh does not word-split unquoted parameter expansions, so it looks for one
command literally named "gtimeout 30", fails, and prints ASIDE_NOT_RUNNING
against a healthy Aside. Every browse-family skill then falls back to $B, and
zsh users (the macOS default shell) lose their real signed-in sessions.
The deadline is now a _gs_bounded() function passing "$@", which needs no word
splitting and behaves the same in zsh, bash and sh. The two string-preserving
fixes both regress someone: ${=_T} is zsh-only and breaks POSIX sh, and eval
breaks the stock-macOS path. The perl fallback string only works when it is
word-split and never re-parsed, so eval turns it into "syntax error near
unexpected token '('" in every shell, including bash users on a stock Mac who
detect Aside correctly today. Inside the function the perl program is quoted,
and the deadline is verified to fire (alarm 2 vs sleep 10: exit 142 after 2s).
The failure branch now captures the probe output and prints PROBE_OUTPUT,
so "No browser window is open for account ..." (Aside running, profile signed
out or windowless) is distinguishable from the app being down. Rule 2 already
asked agents to quote the probe output verbatim; the old grep -q discarded it.
test/aside-driver.test.ts asserted the buggy strings verbatim, which is how the
bug survived. It now asserts the function form and guards against the string
form returning. Regenerated with gen:skill-docs; the three ship goldens get the
same block by surgical replacement.
Refs garrytan#2842, garrytan#2824.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b61c94b to
feaa30c
Compare
…Aside The probe built its deadline into `_T` and expanded it unquoted, so `$_T aside repl …` only worked in a shell that word-splits. zsh does not: it looked for a command literally named "gtimeout 30", the probe answered ASIDE_NOT_RUNNING with Aside installed and ready, and every browsing skill fell back to the bundled Chromium in silence. zsh is the macOS default and Aside is macOS-only, so on a stock Mac the probe could never report READY. The deadline becomes a function. It receives the command as "$@", already split, so sh, bash and zsh all behave the same, and the gtimeout → timeout → perl alarm chain is unchanged. A 4th arm runs the call unbounded when none of the three is present, which is what the empty `_T` did before. Not `eval`: it re-parses the string, so the parens and `;` of the perl arm become syntax and that arm dies in bash *and* zsh. perl is the arm that runs on a stock Mac — no coreutils, no GNU timeout — so routing through eval trades the zsh bug for a regression on the default macOS install. The test pinned the broken invocation verbatim, so it now pins the function and asserts neither `$_T aside repl` nor an eval form comes back. The rationale for that shape lives in the resolver, not in the emitted bash. The block ships inside 19 browsing skeletons, and four of them have less headroom than one comment line: plan-ceo-review 48 bytes, plan-devex-review 55, plan-eng-review 60, and qa 43 before its 1.08 union ratio. Emitting the explanation cost 154 bytes per skeleton and put all four over their guards; carrying it as a TS comment costs 32 and clears every one, so no budget moves here.
feaa30c to
9fddc1a
Compare
|
Continued in #2870 — same branch, same fix, with the CI failure resolved. This PR closed itself on the last push: an amended commit force-pushed from a shallow clone came out as a root commit (the amend drops the parent at the shallow boundary), the branch lost its common ancestor with The history is repaired (the head is now a normal child of |
Fixes #2842.
Why (in your own words)
On any machine whose shell is zsh — the macOS default, and what agent harnesses run their shell tool as — the BROWSER SETUP probe answers
ASIDE_NOT_RUNNINGwhile Aside is running fine. The status line is the small part. The damage is that every browsing skill then silently drops to the bundled headless browser, which has none of the user's cookies or logged-in sessions, so a page behind a login comes back signed-out and the skill blames the site instead of the probe. #2810 made Aside the browser gstack drives first; under zsh that contract was unreachable, and Aside only ships for macOS, where zsh is the shell.The probe holds its deadline in a variable and expands it unquoted, so it only becomes two words in a shell that word-splits unquoted expansions. zsh does not — it looks for one command named
gtimeout 30. The variable is the vehicle for all three deadline arms, so under zsh every arm fails, including theperlarm that is the only one present on a stock Mac.This change makes the deadline a function, which takes the command as
"$@", already split, and behaves identically in sh, bash and zsh.Live evidence
Environment: macOS 15 (Darwin 25.6.0), zsh 5.9, bash 3.2, aside 1.26.906.1630 — installed, signed in, answering
aside replin ~5 ms throughout. Each row narrowsPATHso a different deadline arm is selected; the probe is extracted verbatim from the renderedbrowse/SKILL.md.The mechanism, isolated:
Before —
mainat v1.87.0.0. Aside was up the whole time:After — this branch:
READYis the full lineREADY: aside 1.26.906.1630in every cell; trimmed here for width.Why not
eval.evalsplits in all three shells and is the obvious one-liner — this PR carried it until I measured theperlarm.evalre-parses the string, so the parens and the;inperl -e alarm(shift);exec(@ARGV) 30stop being data and become syntax:A stock Mac has no Homebrew coreutils and no GNU
timeout, but does have/usr/bin/perl— soevaltrades the zsh bug for a regression on the default macOS install, and takes bash down with it. Recorded here so the next person doesn't reach for it. The same reasoning rules out${=_T}(zsh-only): anything that keeps the deadline a string has to re-split or re-parse it.Tests:
Scope
scripts/resolvers/aside.ts— the deadline becomes_gs_bounded(), same gtimeout → timeout → perl alarm order, plus a 4th arm that runs the call unbounded when none of the three exists (what the empty_Talready did).test/aside-driver.test.ts— the assertion pinned the broken invocation verbatim, so it now pins the function, asserts the 4th arm, and asserts neither$_T aside replnor aneval … aside replform can come back. The 19 generatedSKILL.mdfiles and the 3test/fixtures/golden/fixtures are regenerated, not hand-edited (bun run gen:skill-docs; goldens rendered the waytest/host-config.test.tsrenders them).browse/SKILL.mdunder zsh, bash and sh, once per deadline arm (PATH narrowed to select each), before and after, with Aside live — the tables above. Plus the opt-out path and the two test files.command -v asideand the fallback browser takes over — that path is unchanged). Noclaude -pE2E run. I did not exercise a machine whereasideexists but the app is genuinely down; theASIDE_NOT_RUNNINGbranch is unchanged by this PR.Liveness proof (required for external contributors)
Authenticated login:
gh api user --jq .login→AntonioVitalic.Checklist
GSTACK PRtyped live into a real surface) or PR author is @garrytan (owner exemption)Related
#2848 and #2854 report the same defect and land on the same shape of fix, which suggests a function is the answer rather than a taste call. Two notes for whoever triages:
;stays inside a word and the arm runs. It breaks only once the string goes througheval— the table above.else "$@") is load-bearing: with none of the three present, today's empty_Tstill runs the call, and a function without that arm would drop it.Introduced in #2810.