Skip to content

fix: Aside readiness probe reports a false ASIDE_NOT_RUNNING on zsh - #2854

Open
csarigoz wants to merge 1 commit into
garrytan:mainfrom
csarigoz:fix/aside-probe-zsh-word-splitting
Open

fix: Aside readiness probe reports a false ASIDE_NOT_RUNNING on zsh#2854
csarigoz wants to merge 1 commit into
garrytan:mainfrom
csarigoz:fix/aside-probe-zsh-word-splitting

Conversation

@csarigoz

Copy link
Copy Markdown

Why (in your own words)

On any machine whose shell is zsh, the BROWSER SETUP probe reports
ASIDE_NOT_RUNNING while Aside is running perfectly. The status line is the
small part. The real damage is that every browsing skill then drops to the
bundled headless browser, which has none of the user's cookies or signed-in
sessions — so a page behind a login comes back signed-out with no error, and
the skill blames the site instead of the probe. #2810 made Aside the browser
gstack drives first; on zsh hosts that contract was quietly unreachable.

I hit this doing real outreach work: I needed live newsletter metrics off a
media-kit page, the probe said Aside was down, and I spent the task on the
headless fallback before noticing Aside had been up the whole time.

The cause is a bash-ism. The probe builds its deadline into a variable and
invokes it unquoted:

_T=""; command -v gtimeout >/dev/null 2>&1 && _T="gtimeout 30"
elif $_T aside repl 'console.log("ASIDE_READY " + pwd)' ...

zsh does not word-split unquoted parameter expansions, so $_T expands to one
word and it execs a command literally named "gtimeout 30". Command not found,
sentinel missed, false negative. The perl branch was broken on bash too —
unquoted, the ; in _T="perl -e alarm(shift);exec(@ARGV) 30" split it into
two commands.

Fix applies the deadline through a shell function, which behaves the same in
bash, zsh, and sh. The gtimeout → timeout → perl alarm chain, the
GSTACK_SKIP_ASIDE=1 opt-out, and the version echo all survive.

Live evidence

Environment: macOS 15 (Darwin 25.6.0), zsh 5.9, aside 1.26.906.1630.

The false negative, before the fix. Aside was running throughout:

$ # the shipped probe, verbatim from browse/SKILL.md
ASIDE_NOT_RUNNING

$ # the same readiness call, run bare
$ aside repl 'console.log("ASIDE_READY " + pwd)'
ASIDE_READY /Users/…/.aside/u/0/sessions/2026-09-12_NGeOq0yyEg8vCK3U
[ok | 4ms]

The mechanism, isolated:

$ _T="gtimeout 30"
$ $_T echo hi          # unquoted, as the probe does it
(eval):4: command not found: gtimeout 30
$ ${=_T} echo hi       # zsh word-split forced
hi

After the fix — the probe extracted from the regenerated browse/SKILL.md
and run under each shell:

zsh (the shell Claude Code uses): READY: aside 1.26.906.1630
bash:                             READY: aside 1.26.906.1630
sh:                               READY: aside 1.26.906.1630
opt-out honored:                  NEEDS_ASIDE

The perl branch still enforces its deadline (2s alarm against a 10s sleep):

$ _gs_t() { perl -e "alarm(shift); exec(@ARGV)" 2 "$@"; }; _gs_t sleep 10
Alarm clock: 14
exit=142
real 2.012s

End to end through the fixed path — the same page that started this,
now read through Aside rather than the fallback:

$ aside repl '... openTab("https://www.passionfroot.me/heynews") ...'
URL=https://www.passionfroot.me/heynews
TEXT_START … Subscribers 3.2K … Open rate 35.2% … TEXT_END
GSTACK_STEP_OK
[ok | 2178ms]

Tests:

$ bun test test/aside-driver.test.ts
 28 pass  0 fail  249 expect() calls

$ bun test test/aside-driver.test.ts test/aside-render.test.ts \
    test/host-config.test.ts test/ship-review-loop.test.ts \
    test/build-script-shell-compat.test.ts test/binding-template-drift.test.ts \
    test/skill-validation.test.ts
 522 pass  2 fail

The 2 failures are name: gstack-ship / name: gstack-codex vs the
unprefixed upstream names — pre-existing on any skill_prefix: true install
after gstack-relink, present before this branch, and untouched by it. The
goldens in this PR are regenerated unprefixed so they stay correct for a clean
checkout.

Scope

  • Changed: scripts/resolvers/aside.ts (the probe, single source for
    {{ASIDE_SETUP}} and {{ASIDE_RESEARCH}}); test/aside-driver.test.ts
    (pins moved to the function form, plus a regression pin rejecting any $_T
    word-splitting reliance); the 20 regenerated SKILL.md files; the three ship
    goldens; llms.txt and the agents digest from the same regen.
  • Verified live by: running the shipped probe under zsh, bash and sh;
    confirming the opt-out; confirming the perl deadline fires; driving a real
    page through Aside end to end; the test runs above.
  • Did NOT test: Linux or Windows hosts (no gtimeout, so the timeout
    branch is the live one there — exercised only via the isolated function
    test above, not a full run). Did not run the complete bun test suite: it
    hung with no output past 10 minutes on this machine, before and independent
    of this change.
  • No CHANGELOG or VERSION edit — the release commits suggest those are
    yours to cut. Happy to add an entry if you want it in the PR.

Liveness proof (required)

Not attached yet — @csarigoz will add it as the first comment on this PR.

Being straight with you about why, because the check exists precisely for this
case. The code here was written by Claude Code in Cagri's session, on his
machine, against a bug he hit doing real work. He authorized opening the PR.
The liveness screenshot attests that a human opened it, so producing one from
inside the agent session would be manufacturing exactly the evidence the check
is designed to catch — no matter that the terminal and the typing would be
real. That's not Cagri's to waive on your behalf, so it is left for him.

Close this if an unattested PR is not worth your queue time. The diff and the
evidence above stand on their own either way, and the bug is reproducible on
any zsh host in two lines:

_T="gtimeout 30"; $_T echo hi
# zsh: command not found: gtimeout 30      <- probe dies here, reports ASIDE_NOT_RUNNING
# bash: hi

Checklist

  • Liveness screenshot attached — pending from @csarigoz, see above
  • This is not a generated-file-only diff (I edited the source/template and regenerated)
  • No ETHOS.md edits, and no changes to voice / founder perspective / YC references
  • New public command / external service / host adapter has an accepted issue linked (or N/A) — N/A
  • Linked issue or reproduction: no existing issue found; reproduction is in the two-line snippet above and the Live evidence section

Authored by Claude Code (Opus 5) in @csarigoz's session. Regression-pinned in
test/aside-driver.test.ts so the bash-ism cannot return.

The BROWSER SETUP probe built its deadline command into a variable
(`_T="gtimeout 30"`) and then invoked it unquoted (`$_T aside repl ...`),
relying on bash word-splitting. zsh does not word-split unquoted parameter
expansions, so it tried to exec a single command literally named
"gtimeout 30", got `command not found`, missed the ASIDE_READY sentinel, and
reported ASIDE_NOT_RUNNING on a machine where Aside was running fine.

The consequence is worse than a bad status line: every browsing skill then
silently falls back to the bundled headless browser, which carries none of
the user's cookies or signed-in sessions. A page behind a login returns a
signed-out view with no error, and the skill blames the site rather than the
probe. On zsh hosts this made the "Aside is the browser gstack drives first"
contract (garrytan#2810) unreachable.

The perl branch was broken on bash too: unquoted, the `;` in
`_T="perl -e alarm(shift);exec(@argv) 30"` split it into two commands.

Applies the deadline through a shell function instead, which behaves
identically in bash, zsh, and sh. Keeps the gtimeout -> timeout -> perl alarm
chain, the GSTACK_SKIP_ASIDE=1 opt-out, and the version echo.

Verified on macOS 15 (zsh 5.9), aside 1.26.906.1630:

  before, zsh:  ASIDE_NOT_RUNNING   (Aside running the whole time)
  after,  zsh:  READY: aside 1.26.906.1630
  after,  bash: READY: aside 1.26.906.1630
  after,  sh:   READY: aside 1.26.906.1630
  after,  GSTACK_SKIP_ASIDE=1: NEEDS_ASIDE

Fix lands in scripts/resolvers/aside.ts, so it reaches both {{ASIDE_SETUP}}
and {{ASIDE_RESEARCH}} and all 20 skills that carry the probe. Test pins
updated to the function form, plus a regression pin rejecting any `$_T`
word-splitting reliance. The three ship goldens are regenerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqkBGJEhwh8H6FMH4BuUdu
@trunk-io

trunk-io Bot commented Sep 12, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

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

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.

1 participant