feat(itmux): production parity + claude/codex support#243
Open
NeuralEmpowerment wants to merge 10 commits into
Open
feat(itmux): production parity + claude/codex support#243NeuralEmpowerment wants to merge 10 commits into
NeuralEmpowerment wants to merge 10 commits into
Conversation
…in-container (DooD) Syntropic137 runs this driver INSIDE a container (docker-out-of-docker), so `docker run -v host:container` bind mounts for credentials are wrong: a sibling `-v` is resolved by the OUTER daemon against its own filesystem, which cannot see this driver's own staging dir. The Rust port previously did exactly that (`auth::Mount` -> `-v` args in `Workspace::start`), so in production every agent started unauthenticated. Ports the Python driver's fix (driver/interactive_tmux.py PY:1493-1583, PY:1850-1869) bit-for-bit: - `auth::prepare` now returns `PreparedAuth` (staged host path -> in- container destination), not `-v` bind-mount args. - `Workspace::start` runs a bare `docker run ... sleep infinity` with no credential mounts, then calls `auth::stage_into_container` to push each staged file/dir in over `docker exec` stdin: files as base64 through `base64 -d`, directories file-by-file (mirrors Python's `os.walk` transfer exactly, not a tar stream). Credentials never appear in argv (world-readable via `ps`/`/proc/<pid>/cmdline`) - only ever in stdin. - After transfer, every staged path is chowned to 1000:1000 (the in- container `agent` user) and locked to 0600 for files (`chmod 600` via `find` for directory trees), for claude, codex, and gemini - not just the 2 claude files as before. - `docker_exec_with_stdin` added to tmux.rs (feeds stdin from a dedicated thread to avoid a pipe-buffer deadlock against `wait_with_output`). - The transfer/secure command plan is built by pure functions (`write_bytes_plan`, `transfer_dir_plan`, `secure_path_plan`, `plan_for_staged_path`) so it's assertable in tests without a docker daemon; `tests/cred_transfer.rs` covers the no-`-v` docker run argv, the staged destination shape, stdin-only payload delivery, and the chown/chmod plan for both files and directories. No new dependencies: added a small std-only base64 encoder rather than pulling in the `base64` crate for one encode call (decode happens in-container via the coreutils `base64 -d`).
Every docker/tmux shell-out previously used Command::output() unbounded, so a wedged docker exec could hang the whole workflow forever. Add run_bounded/wait_bounded (std-only, spawn + mpsc::recv_timeout, kill on timeout) and route docker_exec, docker_exec_with_stdin, and the docker run/rm call sites through it, matching the Python driver's DEFAULT_EXEC_TIMEOUT_S (15s, PY:86) and DEFAULT_RUN_TIMEOUT_S (30s, PY:87).
Wire the Rust itmux driver into repo-wide QA gates, matching the existing Python justfile/qa.yml conventions: - justfile: add rust-test, rust-lint, rust-fmt-check, rust-fmt recipes and fold them into fmt/fmt-check/lint/test so qa, ci, and check all cover the Rust crate. - .github/workflows/rust.yml: new workflow mirroring qa.yml's trigger/concurrency shape, installs stable Rust via dtolnay/rust-toolchain@v1 (clippy + rustfmt components), caches via Swatinem/rust-cache@v2.9.1, then runs cargo fmt --check, cargo clippy --all-targets -D warnings, and cargo test.
Whole-stack review flagged the credential path rides on this hand-rolled encoder being byte-exact while the container-side base64 -d only runs under real docker. Locks it with RFC 4648 vectors + high-bit/padding cases.
codex-cli 0.139.0's ~/.codex mixes a tiny auth/config surface (auth.json,
config.toml) with hundreds of megabytes of operational state under dirs the
old {tmp,log,logs,plugins} denylist never matched (.tmp/, sessions/,
logs_2.sqlite*, computer-use/, cache/, ...). Staging copied all of it
file-by-file over docker exec base64, turning start_workspace into a
multi-minute crawl that never completed - so itmux start --agents codex never
reached ready (claude, whose surface is one small file, works fine). The
--startup-timeout bound covers only the post-launch readiness wait, not
credential staging, so the symptom was a hang, not a timeout.
Switch codex staging from a fragile denylist to an allowlist of the actual
auth surface (auth.json, config.toml, config.json, AGENTS.md). An allowlist
cannot regress when codex adds new state directories. Empirically validated
that auth.json + config.toml is sufficient for codex to reach its ready
composer. Kept byte-for-byte parity between the Rust and Python drivers.
Capture the real codex-cli 0.139.0 first-ready pane after itmux start --agents codex (trust banner accepted, hooks modal dismissed, composer visible) as a fixture and assert codex_is_ready / codex_is_started match it. Guards the start-path predicate now that the staging fix lets start_workspace reach this pane.
Rust DirEntry::file_type() stats the link itself, not its target, so an allowlisted auth.json/config.toml that is a symlink (common with dotfile managers like stow/chezmoi) was silently dropped - the exact files the allowlist exists to preserve. Use fs::metadata (follows symlinks) for the file-type decision so a symlink-to-regular-file is staged by-value. Restores parity with the Python driver, whose Path.is_file() already follows symlinks. Also assert the full declared allowlist (config.json, AGENTS.md) in the allowlist test, and add a unix symlink test proving symlinked auth files are staged with their target's bytes.
This was referenced Jul 7, 2026
This was referenced Jul 7, 2026
There was a problem hiding this comment.
Pull request overview
This PR consolidates the Rust itmux driver work to reach production parity for Claude + Codex workspace startup, focusing on reliable credential staging in Docker-out-of-Docker environments, bounded shell-outs, and readiness/await resilience. It also adds CI + just integration and expands the Rust test suite with regression fixtures to lock in the parity behavior.
Changes:
- Replace credential bind mounts with post-
docker runcredential transfer viadocker execstdin (base64), plus in-container permission lockdown. - Add bounded timeouts for docker/tmux shell-outs, resilient polling classification (retry vs container-dead), and a large-payload tmux send path using load-buffer/paste-buffer with bracketed paste.
- Wire Rust into repo automation (Just + GitHub Actions) and add regression/fixture tests for Codex readiness, poll resilience, timeouts, and send-literal planning.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| providers/workspaces/interactive-tmux/driver/interactive_tmux.py | Switch Codex staging from a denylist copy to a small allowlist of auth/config files. |
| providers/workspaces/interactive-tmux/driver-rs/src/auth.rs | Implement staged-path model + docker exec stdin transfer plans, Codex auth allowlist, base64 encoder, and secure-in-container steps. |
| providers/workspaces/interactive-tmux/driver-rs/src/tmux.rs | Add bounded subprocess execution, stdin-fed docker exec, and send-literal paste-buffer fallback for large payloads. |
| providers/workspaces/interactive-tmux/driver-rs/src/workspace.rs | Provision bare container (no -v), stage credentials via exec, and add poll classification + container-dead detection. |
| providers/workspaces/interactive-tmux/driver-rs/src/result.rs | Add AwaitResult::container_dead classification to surface target-death vs transient capture failures. |
| providers/workspaces/interactive-tmux/driver-rs/tests/cred_transfer.rs | Add parity tests asserting exec-stdin credential transfer planning and secure steps. |
| providers/workspaces/interactive-tmux/driver-rs/tests/exec_timeout.rs | Add tests validating bounded execution kills/returns promptly on timeouts. |
| providers/workspaces/interactive-tmux/driver-rs/tests/poll_resilience.rs | Add tests for retry-vs-dead poll classification (including daemon-outage resilience). |
| providers/workspaces/interactive-tmux/driver-rs/tests/send_literal.rs | Add tests for send-literal planning and the paste-buffer/bracketed-paste path over threshold. |
| providers/workspaces/interactive-tmux/driver-rs/tests/readiness.rs | Add a live-captured Codex fresh-launch fixture and readiness regression test. |
| providers/workspaces/interactive-tmux/driver-rs/tests/pane_tail.rs | Formatting-only change in an existing test. |
| providers/workspaces/interactive-tmux/driver-rs/tests/fixtures/codex/ready_fresh_launch.txt | Add fresh-launch Codex pane capture fixture for readiness predicate regression coverage. |
| justfile | Add Rust fmt/lint/test tasks and include them in combined targets. |
| .github/workflows/rust.yml | Add a dedicated Rust CI workflow for fmt/clippy/test in the itmux crate directory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+64
to
+72
| // Best-effort: ask the process (and, since docker/tmux | ||
| // children are frequently themselves a fork/exec wrapper, | ||
| // just its own pid - not a process group) to die. We do not | ||
| // have a `Child` handle here anymore (it was moved into the | ||
| // waiter thread so that thread can drain stdout/stderr without | ||
| // deadlocking on a full pipe buffer), so killing by pid via | ||
| // the `kill` utility is the only std-only, `unsafe`-free way | ||
| // to reach it from this side. | ||
| let _ = Command::new("kill").args(["-9", &pid.to_string()]).output(); |
This was referenced Jul 7, 2026
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.
Single consolidated feature PR for the Rust
itmuxdriver work - the whole "make itmux the production driver for claude + codex" effort in one reviewable unit. Supersedes the stacked PRs #231-#236 + #241 (closed in favor of this).This is one of the three layers of the Workspace Standard: itmux (Rust driver). The other two are #240 (Python contract) and APSS #88 (recipe standard).
What's in it (10 commits, oldest first)
65b5ca8-vmounts) + chown/chmod securing. Fixes unauthenticated agents under docker-out-of-docker.492dbc5af5030bcontainer_deadclassification (daemon-outage not misclassified).9c41610-p+ named buffer (multiline prompts paste atomically).dc3f7e5ea55a05fc193f1993e079~/.codexstaging hang).4e8ba53c1d5775auth.json/config.toml).Validation
itmux start --agents claude,codex --strict-startup-> both ready (claude 639ms, codex 66ms), authenticated via the DooD cred transfer.main...HEADdelta.Known follow-up (from the standalone eval)
The claude adapter has a send-race (first keystrokes after launch can drop before input-readiness); fix incoming as a claude-adapter input-readiness gate (per-harness, generalizable). Tracked; not a blocker for review of the above.
Tracks okrs-o78 / okrs-51p.6 / okrs-51p.7.