Skip to content

A terminal beside the agent: zellij in every container (#242) - #243

Merged
blooop merged 2 commits into
mainfrom
wayfinder/devlaunch-242
Aug 16, 2026
Merged

blooop merged 2 commits into
mainfrom
wayfinder/devlaunch-242

Conversation

@blooop

@blooop blooop commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Closes #242

Every container dl launches now has zellij on PATH, and an opt-in switch makes dl <spec> -- <cmd> run beside a named zellij session, so the agent can open a terminal next to itself with zellij -s devlaunch action new-pane -- <cmd>.

The route, and the number that decided it

Container-side pixi. The falsification condition does not fire.

A warm pixi global install zellij inside mcr.microsoft.com/devcontainers/base:ubuntu, against the shared package cache bound the way #240 binds it, over three fresh containers:

Warm install 0.56s, 0.23s, 0.23s
Cold install (empty cache) 3.0s, filling 167MB of shared cache
pixi bootstrap itself (already paid for gh/claude) ~1.0s warm

The ticket's condition was "more than a few seconds". A fifth of a second is not that, so the host-side static-musl-binary mount — faster still, but a new upstream GitHub-release dependency plus a bind mount that only lands at container creation — stays the recorded alternative rather than the implementation.

Two things follow from the route that are worth a reviewer's attention:

  • No mount means no recreate. Provisioning rides the setup pass, which runs on every devpod up, so an existing workspace picks zellij up on its next dl <workspace> restart. The ticket's done-when said "recreate", which was written for the mount route; a recreate also works but is not needed. The README says so in those words.
  • No new upstream dependency, and zellij is a conda-forge package, so it warms the same shared cache everything else uses.

Where it is provisioned from, and why not the obvious place

The tempting change is a third row in REQUIRED_TOOLS. That is a trap, and the tests pin it shut:

REQUIRED_TOOLS is not just an install list — probe_script asks whether all of it is present, and a container answering "missing" is lent the host's ~300MB claude. zellij there would put every container that already holds gh and a real claude back onto the lending path on every launch, and would still never install zellij, because a successful lend returns before the network-install trip is reached.

So zellij is provisioned as a stage of the setup pass instead. That is where "cost the feature, not the launch" already lives structurally: a stage's failure is contained by the if around it, reported by name at its declared level, and cannot change the pass's exit status. The launch lock and _pixi_cache_up_args are the models; this reuses the mechanism rather than restating the rule. zellij_script() is assembled entirely out of the pieces provision_script is assembled from — the pixi bootstrap, the install line, the profile resolution, the hash-guarded prepend — so nothing about how a tool gets installed is written twice.

The wrap, and the judgement call in it

DEVLAUNCH_ZELLIJ=1, default off. Off means no existing invocation changes meaning.

The command runs beside the session, not inside a pane of it, and this is the one place I departed from the ticket's literal wording (zellij attach -c <session> -- <cmd> — it says "or equivalent"). The reasoning:

  • The ticket's own measurement establishes that zellij -s <name> action new-pane works from a command that is in no session at all. The capability needs the session to exist, not to contain the agent.
  • Putting the command in a pane would hand its stdin, stdout and exit status to zellij. All three are contracts this repo holds hard: dl <ws> -- cmd > file must put the command's output in the file, and TestSessionExitStatus exists entirely to keep the remote program's own status intact through devpod's mangling.

Both designs deliver the pane; only one breaks every scripted caller, including every wayfinder agent launch. So the payload is:

bash -lc 'zellij attach -b devlaunch >/dev/null 2>&1 || true; <command>'

attach -b (create detached and return) rather than -c, since the command is not on a terminal. The || true is load-bearing, not defensive — measured: a second zellij attach -b <name> exits 1 once the session exists, which is the case every launch after the first takes. ; and not && so the payload exits with the command's status. A container where zellij never installed swallows a 127 and runs the command exactly as before.

A bare dl <workspace> is untouched, switched on or off. It sends no --command — which is precisely what gets it a pty from devpod — so there is nothing to wrap, and giving it one would cost either the terminal or a ~1.7s round trip in front of every shell (#183's lesson). You land in a login shell with zellij on PATH and zellij attach -c devlaunch reaches the session by hand. This is stated as a test, not just as prose.

One fixed session name rather than one per workspace: a zellij server lives inside a container and dies with it, so two workspaces cannot collide, and a constant is a name a human can type without looking it up.

Verification

Red first. All 23 new tests fail with devlaunch/ reverted to main.

Mutation-checked, because a false green cost this repo a round trip recently. Ten targeted mutations, each breaking exactly one decision, each killing its test and no others:

Mutation Test that catches it
drop the >&2 on the stage test_the_install_never_speaks_on_the_protocols_stdout
drop the warm-path early exit test_a_container_that_already_has_it_installs_nothing
drop the login-PATH prepend test_a_cold_container_gets_it_and_the_next_shell_can_find_it
drop the DEVLAUNCH_NO_TOOLS gate test_the_tools_opt_out_asks_for_no_zellij
promote zellij into REQUIRED_TOOLS test_it_is_never_a_required_tool
&& instead of ; and no || true test_a_session_that_is_already_there_is_not_an_error
make the wrap default-on test_it_is_off_unless_switched_on
wrap a bare attach too test_a_bare_attach_is_untouched_even_switched_on
read the switch with truthiness test_a_switch_set_to_a_denial_is_still_off
hide a failing stage at DEBUG test_a_stage_warns_by_name_unless_it_asks_to_be_quieter

End to end in a real container, running the actual generated scripts non-TTY the way devpod ssh --command runs them:

  • Cold pass: zellij stage ok, stdout carried only marked protocol lines, install noise on stderr where _setup_pass leaves it visible.
  • zellij resolves on the login PATH of a brand-new shell (/home/vscode/.pixi/bin/zellij, 0.44.3).
  • Warm pass: 50ms end to end, nothing installed, stage still ok.
  • Profile PATH line landed exactly once across two passes (the hash-derived mark deduping against provision_script's identical line).
  • The capability: the real wrapped payload opened a pane from a non-TTY command, the pane's side effect landed, the agent's own stdout came back clean, exit status preserved, and a > file redirect captured the command's output and nothing else.

Local CI: exit 0, pylint 10.00/10, 1820 passed. Exit code captured directly, not through a pipeline.

Files changed

  • devlaunch/tools.pyZELLIJ_TOOL, ZELLIJ_STAGE, zellij_script(), and the gated stage in setup_stages
  • devlaunch/dl.pyZELLIJ_WRAP_VAR, ZELLIJ_SESSION, zellij_wrap_enabled(), _with_zellij_session(), and the one-line change at the single wrap point in workspace_ssh
  • test/unit/test_tools.pyTestZellijProvisioning; the new writer registered in TestProfileGuards; three hostname-stage tests narrowed to name the stage they are about now that the pass runs two
  • test/test_dl.pyTestZellijSessionWrap
  • test/test_devpod_spawn_counts.pyTestOptInZellijWrap: the feature adds no devpod round trip on any path
  • README.md, CHANGELOG.md

For the reviewer to weigh

  1. Beside-the-session rather than inside-a-pane — the deliberate reading of "or equivalent", argued above. This is the main design call.
  2. Restart, not recreate — a consequence of the measured route; the ticket's done-when assumed the mount.
  3. A container that cannot install zellij retries on every up, paying one failed pixi attempt each time. The same accepted residual provision_tools documents for the lend retry loop, and for the same reason: breaking the loop needs per-container retry state, which is more machinery than the case is worth.
  4. The zellij stage obeys DEVLAUNCH_NO_TOOLS while the hostname stage deliberately does not — installing zellij is tool provisioning; naming a container is not.

Summary by Sourcery

Add zellij provisioning to all devlaunch workspaces and introduce an opt-in mechanism to run workspace commands alongside a zellij session without changing existing behavior by default.

New Features:

  • Ensure every container workspace has zellij available on PATH via pixi-based installation during the setup pass.
  • Introduce the DEVLAUNCH_ZELLIJ environment switch to run dl -- beside a named zellij session for agent-driven terminal panes.

Enhancements:

  • Extend setup stages to include a gated zellij installation stage that obeys the tools opt-out while preserving hostname behavior.
  • Refine stage outcome parsing and logging expectations to support multiple setup stages and distinct failure levels.

Documentation:

  • Document the zellij-based terminal-beside-the-agent capability, how to enable it, its behavior for interactive and non-interactive sessions, and its performance characteristics in the README.
  • Record the new zellij provisioning and DEVLAUNCH_ZELLIJ opt-in behavior in the changelog.

Tests:

  • Add comprehensive tests for zellij provisioning, including PATH updates, failure handling, opt-out behavior, and absence of extra round trips.
  • Add tests for the zellij session wrap to validate default-off behavior, command quoting, status propagation, and interaction with devpod spawn counts.
  • Update existing stage outcome tests to target specific stages now that the setup pass can include multiple stages.

zellij lands on PATH in every container dl launches, from pixi, container-side,
as a stage of the setup pass that every `devpod up` already pays -- so it costs
no round trip, and a failure is contained and named rather than able to fail a
launch.

The route was decided by the ticket's falsification measurement rather than by
taste. A warm `pixi global install zellij` against the shared package cache cost
0.56s / 0.23s / 0.23s over three fresh devcontainers/base:ubuntu containers
(3.0s cold, filling 167MB), so the host-side static-musl-binary mount -- faster,
but a new upstream GitHub-release dependency and a mount that only lands at
container creation -- stays a recorded alternative.

Deliberately not a third row in REQUIRED_TOOLS: that tuple is also what the
probe asks about and what the host lends, so zellij there would put every
container holding gh and a real claude back onto the ~300MB lending path on
every launch and still never install zellij, because a successful lend returns
ahead of the install trip.

`DEVLAUNCH_ZELLIJ=1` then makes `dl <spec> -- <cmd>` ensure the session exists
before running the command. Off by default. The command runs beside the session
rather than inside a pane of it, because a pane would take its stdin, stdout and
exit status away from dl -- and `zellij -s <name> action new-pane` works from a
command that is in no session at all, so the pane costs nothing either way. A
bare `dl <ws>` attach is untouched: it sends no command to wrap.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @blooop, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR provisions zellij into every devlaunch-managed container via a new setup stage and adds an opt‑in wrapping of remote commands so agents can open panes in a named zellij session, while keeping launch behavior, exit statuses, and spawn counts unchanged by default.

File-Level Changes

Change Details Files
Provision zellij as a pixi-installed tool via a new setup stage that runs on every devpod setup pass, keeping launches robust and avoiding REQUIRED_TOOLS side effects.
  • Introduce ZELLIJ_TOOL metadata and zellij_script() that bootstraps pixi, installs zellij, and prepends ~/.pixi/bin to login PATH with a guarded profile edit.
  • Add ZELLIJ_STAGE and extend setup_stages() to include a zellij stage when tool provisioning is enabled, running the zellij_script via bash -c and redirecting its output to stderr.
  • Ensure zellij provisioning follows the existing stage model: failures are reported by name at WARNING level, never affect probe exit status, and respect DEVLAUNCH_NO_TOOLS opt-out semantics.
devlaunch/tools.py
Add an opt-in zellij session wrap for workspace commands so dl <spec> -- <cmd> can run beside a detachable zellij session without changing default behavior or contract for stdout/exit status.
  • Define ZELLIJ_WRAP_VAR and ZELLIJ_SESSION constants plus zellij_wrap_enabled() to read the DEVLAUNCH_ZELLIJ switch with the same falsey vocabulary as other toggles.
  • Implement _with_zellij_session(command) to prepend `zellij attach -b devlaunch >/dev/null 2>&1
Extend and adjust tests to cover zellij provisioning behavior, PATH/profile guards, stage outcome parsing, opt-out semantics, and the new zellij wrap without altering existing spawn counts or workspace SSH contracts.
  • Expand TestProfileGuards to include the zellij script, assert that its PATH line is deduped via the existing hash guard, and update expectations for total PATH-appending lines.
  • Refactor hostname-stage tests to read outcomes by name rather than tuple position, and verify failure_level mappings for HOSTNAME_STAGE vs ZELLIJ_STAGE.
  • Add TestZellijProvisioning to exercise sandboxed setup_script runs across cases: preinstalled zellij, cold installs, failed installs, offline images, stdout vs stderr noise, and DEVLAUNCH_NO_TOOLS opt-out.
  • Introduce TestZellijSessionWrap to verify default-off semantics, correct ensure payload, tolerance of existing sessions, preservation of command exit status, no wrapping of bare attaches, correct quoting behavior, and proper handling of falsey DEVLAUNCH_ZELLIJ values.
  • Add TestOptInZellijWrap to assert that enabling the wrap does not change devpod spawn counts, keeps one-shot commands at two spawns, leaves attaches unchanged, and never sends a dedicated zellij-only ssh trip.
test/unit/test_tools.py
test/test_dl.py
test/test_devpod_spawn_counts.py
Document the zellij capability, opt-in wrap behavior, restart semantics, and performance characteristics in README and CHANGELOG.
  • Add a README section explaining zellij on PATH in every workspace, how to open panes from inside a session (zellij -s devlaunch action new-pane -- <cmd>), how to enable DEVLAUNCH_ZELLIJ, and why commands run beside the session rather than inside a pane.
  • Clarify that existing workspaces pick up zellij on restart, not recreate, and that provisioning can fail without breaking launches or commands, respecting DEVLAUNCH_NO_TOOLS.
  • Update CHANGELOG with entries describing container-side pixi-based zellij provisioning, the lack of extra round trips, failure behavior, and the DEVLAUNCH_ZELLIJ opt-in wrap for commands.
README.md
CHANGELOG.md

Assessment against linked issues

Issue Objective Addressed Explanation
#242 Provision zellij into every container devlaunch launches (preferably via container-side pixi), ensuring it is on PATH without relying on dotfiles or devcontainer.json edits, and that provisioning zellij can never fail or block a workspace launch; measure pixi install cost to decide between pixi and static-binary mount and record the choice.
#242 Add an opt-in, default-off wrap for dl <spec> -- <cmd> such that commands run in the presence of a named zellij session (enabling zellij -s <name> action new-pane from inside the agent), with bare dl <workspace> attach behavior remaining consistent and the wrap degrading safely when zellij is unavailable.
#242 Document the zellij capability in README (including how to enable it, the -s <name> pane syntax, and how existing workspaces pick it up), and add a corresponding entry under the ## [Unreleased] section of the CHANGELOG.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.19%. Comparing base (c89536f) to head (c18ff8a).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #243      +/-   ##
==========================================
+ Coverage   93.16%   93.19%   +0.02%     
==========================================
  Files          25       25              
  Lines        3715     3730      +15     
==========================================
+ Hits         3461     3476      +15     
  Misses        254      254              
Files with missing lines Coverage Δ
devlaunch/dl.py 95.45% <100.00%> (+0.02%) ⬆️
devlaunch/tools.py 96.78% <100.00%> (+0.09%) ⬆️

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Four review residues, none of which changed what the feature does.

Spec found the one factual overstatement: "a bare `dl <ws>` attach is
untouched" is true of the *session* and not of the command that can
precede it. `attach_workspace` puts the opt-in dotfiles refresh in front
of the shell, and a refresh is a command, so with both switches on it is
wrapped like any other. The effect is benign -- the session is waiting
when the shell arrives -- but the claim as written was false, so the
README and CHANGELOG now say the narrower true thing, and a test pins the
composition rather than leaving the two switches assumed to compose.

Standards found a trap rather than a bug: `provision_script`'s `tools`
argument parameterizes which tools are installed and not the profile
lines, so `provision_script((ZELLIJ_TOOL,))` would still write the
claude-shim prepend. That is exactly why `zellij_script` assembles itself
from the shared helpers, and the docstring now says so at the definition
instead of leaving the next reader to discover it.

`set -u` in `zellij_script`, for the same reason `provision_script` sets
it: they are the same helpers. Its `exec >&2` stays deliberately absent,
since a stage cannot redirect itself.

And `setup_script` now accepts the stages its caller already built.
`setup_stages` stopped being a pure function of `workspace` when the
zellij stage became conditional, so `_setup_pass` was reading the
environment twice -- once for the script it sends and once for the list
it matches the outcomes against. A disagreement would have surfaced as a
phantom `not reached`.

pixi run ci: exit 0, 1821 passed, pylint 10.00/10.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Two independent fresh-context review axes (Standards, Spec), run in parallel; findings are not merged or reranked across axes. Fixed point: merge-base with main (c89536f); diff reviewed at head fc275d4. Spec = ticket #242 and its comments.

Standards

Gates, run first-hand on a fresh clone. pixi run ci real exit 0; pylint 10.00/10; ty clean; 1820 passed / 24 deselected. Matches the PR's claims exactly.

Red-ability, verified rather than read. Reverting devlaunch/ to the merge-base fails all 23 new tests. Because most of that is AttributeError on new names — which proves little — the axis ran five mutations of its own instead of trusting the PR's table: dropping >&2 on the stage, dropping the warm early-exit, forcing the stage past provisioning_disabled(), ;&& in the wrap, and dropping the profile prepend. Each killed exactly the test it should. TestZellijProvisioning drives real bash against a sandboxed $HOME/PATH with faked pixi/curl, so these are behavioural greens, not fixture-shaped ones. No false-green pattern found.

Non-blocking — a trap in the neighbour, not a bug here. zellij_script() repeats provision_script's skeleton, and the docstring argues the pieces are shared, which is true. But the reason it cannot simply call provision_script((ZELLIJ_TOOL,)) is that provision_script's profile lines ignore its tools argument entirely — verified: that call still emits the claude-shim prepend. The duplication is correct; what was missing was the reason being written down where the next reader meets it.
[Resolved during review — provision_script's docstring now states the limit at the definition site: tools parameterizes which tools are installed and not what they need on PATH, and the profile lines must be parameterized before it is reused. Left as documentation rather than a profile_lines refactor, since no production caller passes tools at all.]

Nit — set -u divergence. zellij_script() had neither set -u nor exec >&2 where provision_script has both. The >&2 is deliberate and explained; the missing set -u looked like an omission.
[Resolved during review — set -u added, with the exec >&2 absence now stated as the deliberate half: a stage cannot redirect itself, because _stage_snippet interpolates it into if <command>; then.]

Nit — setup_stages() evaluated twice per pass. Harmless while it only interpolated a workspace id; now that it reads provisioning_disabled(), the script _setup_pass sends and the list it matches outcomes against are two separate readings of the environment, which can in principle disagree and yield a phantom not reached.
[Resolved during review — setup_script takes an optional stages, and _setup_pass passes the tuple it already built.]

Nit, carried not fixed. os.environ.get(X, "").strip().lower() not in _FALSEY is now the 5th verbatim copy across four modules. Pre-existing; this PR extends it by one, and no test pins the vocabulary as shared.

Axis verdict: Approve

Spec

Every Done-when landed, each checked against the ticket's own wording.

"Every container devlaunch launches has a working zellij on PATH… no dependence on the user's dotfiles and no edit to any repo's devcontainer.json" — the axis ran the branch's real setup_script("revws") non-TTY in devcontainers/base:ubuntu: stage zellij ok, and a brand-new login shell resolved /home/vscode/.pixi/bin/zellij (0.44.3). Warm re-run 0.013s, profile line landed exactly once.

"Provisioning zellij must not be able to fail a launch… the wrap degrades to running the command directly" — verified structurally and live: with the hostname stage failing 1 the zellij stage still ran, and in a container with no zellij the wrapped payload printed sh: zellij: not found on stderr, put only AGENT_STDOUT in the redirect file, and exited 7. Both halves of the contract hold.

"An opt-in wrap, default off" — the switch reads os.environ.get(ZELLIJ_WRAP_VAR, "") against _FALSEY after .strip().lower(), so unset is off and a truthiness read would break it (confirmed by mutation).

Falsification condition, re-measured independently. The ticket said "Measure it and put the number in the PR; do not choose on taste." A number is in the PR, and the axis re-measured it on its own containers: warm 0.43s / 0.20s / 0.83s, cold 2.27s, cache 167MB — same order as the PR's 0.56/0.23/0.23 and 3.0s. The condition does not fire; the container-side pixi route is justified by measurement.

All three declared deviations upheld, each checked at the source. Not in REQUIRED_TOOLS: probe_script gates on _all_present(REQUIRED_TOOLS) and provision_tools returns True on a successful _transfer before reaching provision_script, so a zellij row really would make a gh+claude container re-lend every launch and still never install zellij. Beside rather than inside a pane: covered by the ticket's own "or equivalent", and verified live — the pane opened from a non-TTY command and exit 7 survived. restart rather than recreate: restart calls workspace_stop then workspace_up, which pays the setup pass.

Non-blocking. "A bare dl <workspace> is untouched, switched on or off" is stricter than the code: attach_workspacedotfiles_updateworkspace_ssh(command=…), so with the refresh opted in a bare attach does get the ensure prepended and the session created.
[Resolved during review — I reproduced the path independently before acting. The effect is benign, arguably the nicest arrival there is, but the claim was false as written: README and CHANGELOG now say the narrower true thing, and a new test pins the two switches composing rather than leaving it assumed. Verified red-able by neutralizing the wrap.]

Non-blocking. Done-when 1's "every container" is narrowed by DEVLAUNCH_NO_TOOLS — deliberate, argued in code, and documented in README; flagged only because the ticket's wording is unconditional.

Nit. The PR's mutation table claims each mutation killed its test "and no others"; of four re-run, three reddened extra tests. All in the safe direction, and the named test reddened in every case.

Axis verdict: Approve

Verdict

Approve at c18ff8a. Both axes approve independently, and the two things this PR could most easily have got wrong were checked by measurement rather than by reading: the route decision was re-measured on fresh containers by an axis that did not write it, and the "cannot fail a launch" guarantee was observed live in a container with no zellij, where the command still ran and returned its own exit status. The one factual overstatement — "a bare attach is untouched" — was real, and I reproduced it myself before narrowing the prose and pinning the true behaviour in a test.

Worth recording beyond this PR: the builder declined the ticket's own suggested mechanism (zellij as a third row in REQUIRED_TOOLS) and was right to, because that tuple also drives the probe and the lend; a row there would have made every already-equipped container re-lend ~300MB on every launch and still never install zellij. And zellij attach -b <name> exits 1 when the session already exists — the common path — so the || true is load-bearing rather than defensive.

Residues resolved on the branch; pixi run ci exit 0, 1821 passed, pylint 10.00/10. GitHub refuses the approve state on a same-account review, so this written verdict is what the gate reads.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Two independent fresh-context review axes (Standards, Spec), run in parallel; findings are not merged or reranked across axes. Fixed point: merge-base with main (c89536f); diff reviewed at head fc275d4. Spec = ticket #242 and its comments.

Standards

Gates, run first-hand on a fresh clone. pixi run ci real exit 0; pylint 10.00/10; ty clean; 1820 passed / 24 deselected. Matches the PR's claims exactly.

Red-ability, verified rather than read. Reverting devlaunch/ to the merge-base fails all 23 new tests. Because most of that is AttributeError on new names — which proves little — the axis ran five mutations of its own instead of trusting the PR's table: dropping >&2 on the stage, dropping the warm early-exit, forcing the stage past provisioning_disabled(), ;&& in the wrap, and dropping the profile prepend. Each killed exactly the test it should. TestZellijProvisioning drives real bash against a sandboxed $HOME/PATH with faked pixi/curl, so these are behavioural greens, not fixture-shaped ones. No false-green pattern found.

Non-blocking — a trap in the neighbour, not a bug here. zellij_script() repeats provision_script's skeleton, and the docstring argues the pieces are shared, which is true. But the reason it cannot simply call provision_script((ZELLIJ_TOOL,)) is that provision_script's profile lines ignore its tools argument entirely — verified: that call still emits the claude-shim prepend. The duplication is correct; what was missing was the reason being written down where the next reader meets it.
[Resolved during review — provision_script's docstring now states the limit at the definition site: tools parameterizes which tools are installed and not what they need on PATH, and the profile lines must be parameterized before it is reused. Left as documentation rather than a profile_lines refactor, since no production caller passes tools at all.]

Nit — set -u divergence. zellij_script() had neither set -u nor exec >&2 where provision_script has both. The >&2 is deliberate and explained; the missing set -u looked like an omission.
[Resolved during review — set -u added, with the exec >&2 absence now stated as the deliberate half: a stage cannot redirect itself, because _stage_snippet interpolates it into if <command>; then.]

Nit — setup_stages() evaluated twice per pass. Harmless while it only interpolated a workspace id; now that it reads provisioning_disabled(), the script _setup_pass sends and the list it matches outcomes against are two separate readings of the environment, which can in principle disagree and yield a phantom not reached.
[Resolved during review — setup_script takes an optional stages, and _setup_pass passes the tuple it already built.]

Nit, carried not fixed. os.environ.get(X, "").strip().lower() not in _FALSEY is now the 5th verbatim copy across four modules. Pre-existing; this PR extends it by one, and no test pins the vocabulary as shared.

Axis verdict: Approve

Spec

Every Done-when landed, each checked against the ticket's own wording.

"Every container devlaunch launches has a working zellij on PATH… no dependence on the user's dotfiles and no edit to any repo's devcontainer.json" — the axis ran the branch's real setup_script("revws") non-TTY in devcontainers/base:ubuntu: stage zellij ok, and a brand-new login shell resolved /home/vscode/.pixi/bin/zellij (0.44.3). Warm re-run 0.013s, profile line landed exactly once.

"Provisioning zellij must not be able to fail a launch… the wrap degrades to running the command directly" — verified structurally and live: with the hostname stage failing 1 the zellij stage still ran, and in a container with no zellij the wrapped payload printed sh: zellij: not found on stderr, put only AGENT_STDOUT in the redirect file, and exited 7. Both halves of the contract hold.

"An opt-in wrap, default off" — the switch reads os.environ.get(ZELLIJ_WRAP_VAR, "") against _FALSEY after .strip().lower(), so unset is off and a truthiness read would break it (confirmed by mutation).

Falsification condition, re-measured independently. The ticket said "Measure it and put the number in the PR; do not choose on taste." A number is in the PR, and the axis re-measured it on its own containers: warm 0.43s / 0.20s / 0.83s, cold 2.27s, cache 167MB — same order as the PR's 0.56/0.23/0.23 and 3.0s. The condition does not fire; the container-side pixi route is justified by measurement.

All three declared deviations upheld, each checked at the source. Not in REQUIRED_TOOLS: probe_script gates on _all_present(REQUIRED_TOOLS) and provision_tools returns True on a successful _transfer before reaching provision_script, so a zellij row really would make a gh+claude container re-lend every launch and still never install zellij. Beside rather than inside a pane: covered by the ticket's own "or equivalent", and verified live — the pane opened from a non-TTY command and exit 7 survived. restart rather than recreate: restart calls workspace_stop then workspace_up, which pays the setup pass.

Non-blocking. "A bare dl <workspace> is untouched, switched on or off" is stricter than the code: attach_workspacedotfiles_updateworkspace_ssh(command=…), so with the refresh opted in a bare attach does get the ensure prepended and the session created.
[Resolved during review — I reproduced the path independently before acting. The effect is benign, arguably the nicest arrival there is, but the claim was false as written: README and CHANGELOG now say the narrower true thing, and a new test pins the two switches composing rather than leaving it assumed. Verified red-able by neutralizing the wrap.]

Non-blocking. Done-when 1's "every container" is narrowed by DEVLAUNCH_NO_TOOLS — deliberate, argued in code, and documented in README; flagged only because the ticket's wording is unconditional.

Nit. The PR's mutation table claims each mutation killed its test "and no others"; of four re-run, three reddened extra tests. All in the safe direction, and the named test reddened in every case.

Axis verdict: Approve

Verdict

Approve at c18ff8a. Both axes approve independently, and the two things this PR could most easily have got wrong were checked by measurement rather than by reading: the route decision was re-measured on fresh containers by an axis that did not write it, and the "cannot fail a launch" guarantee was observed live in a container with no zellij, where the command still ran and returned its own exit status. The one factual overstatement — "a bare attach is untouched" — was real, and I reproduced it myself before narrowing the prose and pinning the true behaviour in a test.

Worth recording beyond this PR: the builder declined the ticket's own suggested mechanism (zellij as a third row in REQUIRED_TOOLS) and was right to, because that tuple also drives the probe and the lend; a row there would have made every already-equipped container re-lend ~300MB on every launch and still never install zellij. And zellij attach -b <name> exits 1 when the session already exists — the common path — so the || true is load-bearing rather than defensive.

Residues resolved on the branch; pixi run ci exit 0, 1821 passed, pylint 10.00/10. GitHub refuses the approve state on a same-account review, so this written verdict is what the gate reads.

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.

A terminal beside the agent: zellij in every container, generically (no dotfiles, any image)

1 participant