Repository navigation
fix-wave 1a: autoplan compaction, /cso redaction + OrbStack, browse tab recovery, zsh Bun floor - #3202
Closed
garrytan wants to merge 14 commits into
Closed
fix-wave 1a: autoplan compaction, /cso redaction + OrbStack, browse tab recovery, zsh Bun floor#3202garrytan wants to merge 14 commits into
garrytan wants to merge 14 commits into
Conversation
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…y dispatches executable() returns the canonical path, and OrbStack installs docker as a symlink to docker-tools, which dispatches on argv[0] and exits 127 with 'unsupported argv0 "docker-tools"'. Every Docker call failed, so doctor reported local-docker-isolation and every scanner missing and setup could not preload the qualified images. runProcess takes an argv0 option; docker.ts routes every Docker call through runDocker, which passes 'docker'. The native watchdog's execve calls pass "docker" as argv[0] too, so its container cleanup works on the same installs. The executed file is still the verified canonical path. Fixes #3187
…inary A compiled fake that refuses any argv[0] but docker stands in for OrbStack's docker-tools. Both behavior tests fail on the previous code. Refs #3187
…gh OrbStack's multicall docker (#3187) Follow-up to #3188 by Bryan Stone (@aegixx): - the python3 spawnSync that binds the fake daemon socket gets a 30 s timeout; without it test/spawnsync-timeout-tripwire.test.ts fails. - reviewer test: dockerExactImagePresent, a production probe, admits an image when the endpoint's canonical executable is a multicall binary that refuses any argv0 other than "docker". It fails on 47d639a (returns false) and passes with #3188. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ction Claude Code 2.1.300 keeps a compaction's last records in place and lists them in compactMetadata.preservedMessages. The segment's tail, which is also the boundary's logicalParentUuid, is written after the boundary under the compact summary, so the parent links form a cycle that the owned causal order never reaches. Every record after the compaction was dropped, and the publication guard denied each later phase entry with close_incomplete. Such a boundary now continues from the last preserved record that is a node of the file and not its own descendant. Boundaries without that metadata, with a foreign preserved uuid, or with nothing kept in place still supply no ownership. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… unrecognized_shape (#3194) Follow-up to #3196 by vilano93. After the ownership walk, an owned node the walk never reached was silently dropped, and the phase guard answered the next phase entry with close_incomplete, which the user cannot satisfy. When the unreachable node's ancestry ends in a cycle through a compact boundary's logical parent, ownedCausalOrder now returns unrecognized_shape:compaction_unreachable, the guard's existing unverified disposition (allow with a visible warning and a guard-log row). Orphans whose ancestor is missing (not flushed yet) and other roots keep today's behaviour, so the fail-open surface does not widen beyond compaction. Each node is walked once. Gate-integrity edit: a close_incomplete denial becomes an unverified allow for that shape only. Negative controls kept: the existing guard, hook, guard-reasons and captured-fixture tests still deny (356 pass), and a new case with the boundary not yet flushed still denies close_incomplete. Intentional pin changes in #3196's own test file: the three "fails closed" cases (tail-only segment, missing preserved metadata, foreign preserved uuid) and the "without compactMetadata" case now expect unrecognized_shape:compaction_unreachable instead of close_incomplete or a silently cut transcript. Reviewer tests: three compactions in one session stay owned and pass the next phase entry; a 50k-record history with three compactions is decided in ~300 ms (budget 1000 ms), so descends needs no memo. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
lib/sanitize-echo.ts and bin/gstack-sanitize-echo.sh flatten CR/LF/TAB to spaces, remove every other ASCII control byte (ESC included) and DEL, collapse runs of spaces, and cap the result at 200 bytes of valid UTF-8 with no leading or trailing space. Every external string gstack echoes into a message, a log line or a key=value state file goes through it (fix wave 2026-10-15, plan rev 3 DX F8). test/sanitize-echo.test.ts runs both twins on one table under bash, bash --posix and zsh, including a "\nstages=" injection, ESC sequences and UTF-8 characters cut by the cap. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…able version (#3195) Reported by mochafreddo. _gstack_bun_below word-split $_gb_core, which zsh does not do: `[ -ne ]` errored and every error path read as "not below", so /gstack-upgrade Step 4 accepted Bun 1.3.2 in a zsh tool shell. - _gstack_bun_below copies the release's parsed fields into quoted positional parameters and compares with $((10#...)); no new _gb_* globals. It returns 2 when either side cannot be parsed. - gstack_bun_status prints malformed for an unparseable version or floor (never ok); gstack_bun_incoming_hold holds on it with bun-version-unreadable: ... (BUN_VERSION_UNREADABLE), the bun path and its output passed through gstack_sanitize_echo. setup's own malformed warning is unchanged. - /gstack-upgrade Step 4 runs the check through `bash -c` and stops on BUN_VERSION_UNREADABLE with a reinstall fix (never "bun upgrade"). - New result code BUN_VERSION_UNREADABLE with its troubleshooting entry; bin/gstack-doctor-components.sh regenerated from the table. test/bun-version-shells.test.ts: one version table (1.3.2, 1.3.3-canary.1, 1.3.3, 1.3.14, 1.4.2, 1.10.0, 01.04.02, 2.0.0, garbage) drives bash, bash --posix, zsh and the TypeScript twin's below(); `zsh -c '. bin/gstack-bun-version.sh; gstack_bun_status 1.3.2'` printed ok on 47d639a and prints too-old now. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…3187) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Reported in #3185. The scanner path splices redactions into raw JSON, and redactFindingSpans replaced a bare 10+ digit number ("max_memory_bytes":1145427776, an epoch) with an unquoted marker, so JSON.parse failed and every non-trivial Semgrep run was INVALID_OUTPUT. - lib/redact-engine.ts: redactJsonFindingSpans, a string- and escape-aware lexical tokenizer over one or more top-level values (NDJSON included). A span inside a number replaces that whole token (-, fraction, exponent) with the quoted marker, number by number; a span inside a string keeps the in-string marker, widened to whole escapes; a span touching several tokens, whitespace, a literal or a structural character returns null (withhold). Non-JSON text gets the plain splice. redactFindingSpans is unchanged for every other caller. - The scan operation's declared format rides the real capture path: scanner-executor (scan stdout, or the report file read for outputPath plans) -> DockerGroup.execCapture -> runProcess -> spliceRedaction, which redacts the concatenated stdout once and maps each interleaved run onto it. Version probes and stderr stay plain; marker-only withholding and the cross-stream views check are kept, and the output is a fixpoint of that check. Behavior change: a redacted number in a scanner report is now the string "<REDACTED-...>" instead of an unquoted marker; non-JSON streams keep the unquoted form (test/cso-eval.test.ts unchanged). Intentional pin change: test/cso-scanner-release.test.ts pinned the report read as capture(['/bin/cat', plan.outputPath]); it now pins capture(['/bin/cat', plan.outputPath], plan.format), so the read also carries the declared JSON format (stricter, not weaker). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Closing the last tab outside browse (for example, closing the headed Chromium window) left the daemon wedged: the page 'close' handler removed the tab and set activeTabId to 0, and executeCommand called getActiveSession() first, so every later command (goto, status, even stop) failed with "No active page" until the daemon PID was killed by hand. closeTab() already opens a blank tab when the last tab closes; the external close path never did. Add BrowserManager.ensureActiveTab(), which reselects a remaining tab or opens one blank tab (concurrent callers share one recovery), and call it before resolving the active session in executeCommand. Fixes #3167
… says what it did (#3167) Follow-up to #3179 by aftabkh4n, which reopens a tab on demand. - BrowserManager.ensureCommandTab runs before every command: status, stop and restart never open a tab, so a daemon whose browser or context is gone can still report and be stopped; any other command gets an active tab. A failed recovery is BROWSE_TAB_UNRECOVERABLE carrying the restart command (cause passed through sanitizeEcho), and a command that ran on a newly opened tab prints a one-line warning first. - server.ts reads the session inside the read and write branches instead of before dispatch (2223 lines, cap 2224) and exposes browserManager and handleCommandInternal on __testInternals__ for the dispatch test. - meta-commands.ts: handleMetaCommand no longer reads the active session before its switch (only snapshot and resume used it), and status prints getCurrentUrl() so it works with zero tabs. This file is outside the lane's listed ownership; no other lane or #3190 touches browse/. - New result code BROWSE_TAB_UNRECOVERABLE and troubleshooting entries for it and for the old "No active page" wedge. Reviewer test browse/test/tab-external-close-dispatch.test.ts drives the real dispatch in a child process: status, text, goto and stop after an external close, and the closed-context case. On 47d639a (with only the test seam added) all five fail with "No active page". Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…lds the auto-update (#3195) Item 11 makes every caller of gstack_bun_incoming_hold treat a Bun version it cannot read as a hold (bun-version-unreadable, BUN_VERSION_UNREADABLE). The session-start banner only knew bun-too-old, so the new reason fell through to "git pull did not finish ... gstack is not updating" with a pull-and-setup fix that does not help. - bin/gstack-session-update: a bun-version-unreadable banner case with the reinstall fix and the troubleshooting anchor; the comment that said a malformed version passes is corrected. Intentional pin change: test/session-update-stages.test.ts "supported or unparseable Bun (bun-dev (local build)) proceeds: setup decides" is split. 1.3.14 still proceeds; bun-dev (local build) now holds with BUN_VERSION_UNREADABLE, leaves the checkout and setup untouched, and the banner names the reinstall fix. The fixture installs bin/gstack-sanitize-echo.sh beside the floor helper, as a real install of this release does. The new case fails on 47d639a. Ownership exception granted by the coordinator for Lane A (bin/gstack-session-update is Lane B's file; PR 1b stacks on 1a). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix wave 2026-10-15, layer 1a (bottom of the
capy/fix-wave-2026-10-15stack): recovery, cross-platform. It contains items 1, 5, 6, 7 and 11 ofdocs/designs/FIX_WAVE_2026_10_15.md(plan revision 3, approved by Garry on 2026-10-15, autoplan runrun-20261015160532), plus the shared echo sanitizer. The plan is copied verbatim in its own commit. Each item was reproduced on v1.91.101.0 (47d639a) with a test that failed before the fix. Layer 1b stacks on this branch.Fixes #3194, #3185, #3187, #3167 and #3195.
The PR adopts three external PRs, cherry-picked with their authorship kept and release churn dropped. Our changes sit in separate commits on top.
Behavior changes
/autoplankeeps going after a Claude Code 2.1.300 auto-compaction.lib/claude-journal-records.tsfollows the compact boundary's preserved segment (fix(autoplan): own the conversation after a Claude Code 2.1.300 compaction (#3194) #3196), so the phase guard no longer answers every phase entry withclose_incomplete./autoplanphase guard. Owned records can end up cut off from the session root after the walk. When acompact_boundaryrecord explains that cut-off part, it now maps to the guard's existingunrecognized_shapedisposition: allow, with a visible warning and a decision-log row, under the detailunrecognized_shape:compaction_unreachable.close_incomplete.autoplan-publication-guard,-hook,guard-reasonsandautoplan-publication-boundary-361c.json./csoscanner JSON reports parse after redaction. For scan stdout declared as JSON, a redacted number token becomes the quoted string"<REDACTED-…>"; before, it was an unquoted marker that brokeJSON.parseand producedINVALID_OUTPUT.DockerGroup.execCapture,runProcessand the splice./csoworks with OrbStack's Docker. Docker and the watchdog spawn the trusted realpath withargv0: 'docker', which OrbStack's multicall binary dispatches on. The trust check still runs on the realpath.[browse] warning: every tab was closed outside browse, so this command ran on a new blank tab; open your page again with: browse goto <url>.status,stopandrestartnever open a tab, so a closed context can still be inspected and stopped.BROWSE_TAB_UNRECOVERABLEand the restart command._gstack_bun_belowno longer depends on word splitting, so under zsh Bun 1.3.2 is nowtoo-old; before, zsh accepted it.bun-version-unreadable: … (BUN_VERSION_UNREADABLE). The bun path and its output pass through the shared sanitizer./gstack-upgradeStep 4 runs the check throughbash -c, and onBUN_VERSION_UNREADABLEit stops with a reinstall fix, neverbun upgrade.#bun-version-unreadable; before, the reason would have fallen through to "git pull did not finish".lib/sanitize-echo.tsand its Bash twinbin/gstack-sanitize-echo.shremove control characters, flatten to one line and cap at 200 bytes without splitting a UTF-8 character. The Bun hold and the browse recovery error use it.docs/troubleshooting.mdentry under its exact text:BUN_VERSION_UNREADABLEandBROWSE_TAB_UNRECOVERABLE. Also new entriesbrowse-no-active-page,cso-scanner-invalid-outputandcso-orbstack-argv0, plus theunrecognized_shape:compaction_unreachableparagraph indocs/autoplan-guard-troubleshooting.md.Tests (each failed on 47d639a first)
test/autoplan-compaction-preserved-segment.test.ts. Ours: a three-compaction case, a 50k-record history inside the hook budget (~310 ms against 1000 ms), and an unflushed-boundary negative controltest/cso-json-redaction.test.ts: the plan's number, negative, exponent, array, NDJSON, whitespace and in-string cases; the real version-then-scan capture with stderr interleaved returnsstatus: 'complete'JSON Parse error: Unrecognized token '<')test/cso-docker-argv0.test.ts. Ours: a 30 s timeout on the python3spawnSync, and a production image probe through the multicall binaryExpected: 0 Received: 127)browse/test/tab-external-close-dispatch.test.tsdrives the real dispatch:status,text,gotoandstopafter an external close, plus the closed-context case. Also #3179'sbrowse/test/tab-external-close.test.tstest/bun-version-shells.test.ts: one version table across bash,bash --posix, zsh and the TypeScriptbelow(); a source check that Step 4 invokes bashExpected "too-old" Received "ok")test/session-update-stages.test.ts"unparseable Bun (bun-dev (local build)) holds with BUN_VERSION_UNREADABLE…"test/sanitize-echo.test.tsunder bash,bash --posixand zshIntentional pin changes, each named in its commit:
test/autoplan-compaction-preserved-segment.test.ts(fix(autoplan): own the conversation after a Claude Code 2.1.300 compaction (#3194) #3196's own file): the three "fails closed" cases (tail-only segment, missing preserved metadata, foreign preserved uuid) and the "without compactMetadata" case now expectunrecognized_shape:compaction_unreachableinstead ofclose_incompleteor a silently cut transcript.test/cso-scanner-release.test.ts: the source pincapture(['/bin/cat', plan.outputPath])becomescapture(['/bin/cat', plan.outputPath], plan.format). This is stricter: the report read must also carry the declared format.test/session-update-stages.test.ts: "supported or unparseable Bun … proceeds" is split in two. 1.3.14 still proceeds;bun-dev (local build)now holds withBUN_VERSION_UNREADABLE. The fixture also installsbin/gstack-sanitize-echo.shbeside the floor helper, as a real install of this release does.No other existing assertion was weakened, skipped or renamed.
Coordinator-exception note
lib/result-codes.tsanddocs/troubleshooting.md(this layer does not touchCHANGELOG.mdor thepackage.jsonversion line). The exception covers exactly that set. Upper layers append to them, and v1.91.107.0 fix: the GBRA-40 field list (one Bun for install and check,recgate approval, pregate never silent) #3190's appends are resolved when the wave merges main.bin/gstack-doctor-components.sh: generated from result-codes; resolved by regeneration at merge. It is also a v1.91.107.0 fix: the GBRA-40 field list (one Bun for install and check,recgate approval, pregate never silent) #3190 file. It is never hand-edited here.bin/gstack-bun-version.sh: shared with v1.91.107.0 fix: the GBRA-40 field list (one Bun for install and check,recgate approval, pregate never silent) #3190 (_gstack_bun_below,gstack_bun_statusandgstack_bun_incoming_holdonly; no new_gb_*globals). This is covered by the v1.91.107.0 fix: the GBRA-40 field list (one Bun for install and check,recgate approval, pregate never silent) #3190 pair receipt.bin/gstack-session-update(Lane B's file), commitfix(session-update): …: the coordinator granted Lane A thebun-version-unreadable*)banner case and the corrected comment, and thetest/session-update-stages.test.tschange. PR 1b stacks on this branch.browse/src/meta-commands.ts: outside Lane A's listed files, accepted by the coordinator. fix(browse): recover when the last tab is closed outside the CLI #3179's fix needsstatusto stop reading the active page before dispatch. No other lane or v1.91.107.0 fix: the GBRA-40 field list (one Bun for install and check,recgate approval, pregate never silent) #3190 touchesbrowse/.Validation
bun run test: passed on the frozen head 045a398 on Ubicloud (ubirun-fixwave1a-1792096388-b746080e, standard-16, destroyed afterwards). 17/17 shards, 28,243 tests: 28,152 passed, 91 skipped, 0 failed.cso-scanner-releaseandsession-update-stagespin changes above. Both pass now.PREGATE: requires-remote (4 uncleared) tree=cc9557b41347 head=045a398f31d7 checks=6, run on a Ubicloud standard-16 (ubirun-fixwave1apg-1792096388-c59bdd0a) with the tree clean before and after. The local lane passed (780 files, exit 0); the same lane timed out after 3300 s on the 4-core Capy machine. The preflight passed; secrets iswarnwith 40 MEDIUM non-credential findings (test fixtures: phone, IP and card numbers). The uncleared remote obligations arecso-macos-launcher(6 tests),macos-named-regressions(6),cso-windows-launcher(1) and the Windows free shard (675).bun run typecheckreports zero errors;bun run typecheck:testreports none new against the 865-diagnostic baseline.test/spawnsync-timeout-tripwire.test.ts,test/module-size-ratchet.test.tsandtest/parity-suite.test.tspass (browse/src/server.tsis at 2223 lines against its 2224 cap).bun run format:cso:checkpasses;gen:skill-docsoutput is fresh for all hosts; the troubleshooting-anchor and doctor-components checks pass..context/fixwave-changelog-A.md(copy at~/.capy/work/changelog-A.md).Paid checks (none run)
bun run eval:select(PR profile, dependents mode) selects 108 behavioral probes and 24 quality judges. The new and changed result-code and sanitizer files each select all 244 dependent gate cases; the slice plan is 81 cases in 15 slices, and 136 behaviors are deferred to release coverage.gstack-eval-plan price-gateforbun run eval:bg:prprintedGATE_PRICE: total=unknown(EVAL_GATE_UNPRICED), because the base policy declares noprice_file. No paid spend happened. CI's E2E Evals workflow runs the paid PR gate when this PR opens.What was not verified
cso-macos-launcher,macos-named-regressions,cso-windows-launcherand the Windows free shard.