| name | tmux-factory-codex-go |
| description | Fire-and-get-pinged software factory (tmux-factory edition). You stay on main while it spins up an isolated worktree plus a detached codex tmux session running the full $sf-auto loop (design, implement, review, fix, open PR, squash-merge to main), delivers the task through the tfmux CLI, and binds your pane as the tfmux mediator so codex PINGS YOU when it finishes. A one-line merged or blocked status lands right in your pane. No polling, no daemon, just return to idle, catch the ping, and git pull main. Use the /tmux-factory-codex-go slash command with your feature description. |
tmux-factory — Codex Go (fire-and-get-pinged)
Fire, then get pinged. Kick codex off in its own worktree and get straight back to the main thread — no polling, no waiting. codex pings your pane with a one-line status the moment it squash-merges (or hits a blocker). Stay idle to catch it.
Then stop — don't poll, don't babysit. You (the mediator) already got confirmation: the launcher verified the task landed and that codex actually started the loop — that's the only check you need. So the moment it returns:
- No background watcher, no poll loop — don't fire
gh pr list / tmux capture-pane every N seconds. Busy-waiting right after launch defeats the whole fire-and-get-pinged design.
- The nudge comes to you → codex
tfmux sends a one-line ✅ merged / ⚠️ blocked status straight into your pane. Just return to idle and catch it.
- Want a safety net? Worst case only, one long poll — a single background command that sleeps ~30 min, then checks/reminds once. Never short repeated polls.
Spin up codex in its own worktree running $sf-auto (design → implement → review → open PR → squash-merge to main), deliver the task over tfmux, and bind your pane as the tfmux mediator so codex pings you when done. A bundled script does every mechanical step in one shot — worktree → quarantine hook-copied .llm → dotllm trust + init → bind mediator → spawn → verified tfmux send → arm the done-ping — so you're back on main the moment it returns.
Run from inside the repo you want to build in, on main for the normal isolated-worktree + PR flow.
Outside a git repo
If the current directory is not inside a git repo, the launcher degrades to in-place mode: no branch, worktree, PR, or squash-merge is created, codex starts with cwd set to the directory where you fired the command, dotllm setup is best-effort, and a done/blocked tfmux send mediator ping is still armed. Broken git metadata or other git errors fail loudly instead of falling back. The launch notice says there is no worktree isolation because codex edits that directory directly.
Do this
1. (Rich feature only) capture it verbatim to a file. Multi-line, @paths, or code snippets → Write the exact text to a temp file and pass --task-file (shell quoting would mangle it). One-liners go inline.
2. Run the engine — one command does everything:
eng=~/.claude/skills/tmux-factory-codex-go/scripts/codex-go.sh
[ -x "$eng" ] || eng=~/.config/skl/library/skills/tmux-factory/tmux-factory-codex-go/scripts/codex-go.sh
"$eng" "<feature, verbatim>"
The script: creates the feat/<slug> worktree → quarantines any hook-copied .llm → dotllm trust + init (codex's project root) → derives a per-run tfmux session tfmux-<slug> → (in tmux) binds THIS pane as the tfmux mediator in that session → spawns a detached codex tmux session (codexy, TFMUX_SESSION exported) → waits for codex's composer → binds codex's pane and delivers $sf-auto <feature> via tfmux send (verified: confirms codex started, retries up to 5×) → tells codex to tfmux send mediator a status line when done → best-effort opens the session in a new tmux window via tfmux attach. Prints spawned / worktree / branch / tfmux / ping-back.
3. Surface the output, then return to idle. Don't poll or babysit (see the no-poll callout up top) — the ping comes to you. More features? fire the next one; each uses its own tfmux-<slug> session and pings back independently.
What you just fired
| Owner | Does |
|---|
| the script (one shot) | worktree on feat/<slug> · quarantine hook-copied .llm · dotllm trust + init · derive tfmux-<slug> · bind THIS pane as tfmux mediator · spawn detached codex (TFMUX_SESSION exported) · tfmux send the $sf-auto task (verified) · arm the done-ping · auto-attach window · return |
codex ($sf-auto, unattended in the worktree) | request → design → implement (test-first, commit per task) → review → fix → open PR → squash-merge to main → tfmux send mediator a status line |
| you (idle in your pane) | stay put → catch codex's ping → then always finish the run yourself: verify the squash-merge landed (gh pr list --head feat/<slug> --state merged) → run the printed factory-cleanup.sh ... --pr <PR> (it syncs local main, removes the worktree, kills the session). Never hand the finish to a human. (Didn't actually merge, or ⚠️ blocked → don't clean up; surface it.) |
codex uses the $sf-auto trigger. Don't over-constrain the feature ("change only X") — that can make codex do a literal edit and skip the loop. State what to build, verbatim.
How the ping-back works
tfmux has no global current session — routing is by an explicit named session. The script derives one per run (tfmux-<slug>), binds your pane as mediator in it, and exports TFMUX_SESSION into codex's shell. codex reaches you with its final action:
tfmux send mediator --session tfmux-<slug> --text '✅ <slug> merged: <PR url>'
That line lands in your pane. Stay idle to catch it. Ping-back needs you in tmux (the script binds your pane via --here); if you're not, the script warns and degrades to fire-and-forget — then you check the PR yourself.
Prerequisites (check once)
tfmux — REQUIRED (delivers the task and carries the ping-back). tmux on PATH too. In git mode, wt, dotllm, git, and python3 are preflighted; outside git, dotllm is tried only best-effort.
gh authenticated in git mode — codex opens + squash-merges the PR, and the cleanup helper verifies the merged PR; the script preflights that a git remote exists. Outside git, there is no PR or squash-merge.
- The
shadowfax bundle loaded — codex needs $sf-auto in ~/.codex/skills/.
- Ping-back needs you in tmux. Not in tmux → no mediator pane → no ping; the script warns.
Checking on it (you'll usually be pinged first)
gh pr list --head feat/<slug> --state all
git ls-remote origin -h refs/heads/main
- Got
✅ <slug> merged: … → finish it yourself, always — never leave it for a human. Verify the squash-merge actually landed (gh pr list --head feat/<slug> --state merged), then run the printed factory-cleanup.sh ... --pr <PR_URL_OR_NUMBER> command — it ff-only syncs local main, proves the merge commit is on main, removes the worktree, then kills the session (steps 2–3 in one safe pass). If the PR did not actually merge, surface that to the human instead of cleaning up blind.
- Running / PR open → leave it; watch with
tmux attach -t <NAME>.
- No commit, no PR (over-constrained ask) → re-send:
tfmux send <NAME> --session tfmux-<slug> --text '$sf-auto run your full loop end-to-end to a squash-merged PR for: <feature>'.
Notes
- Worktree-first (load-bearing): the launcher always finishes the worktree — create
feat/<slug> → quarantine .llm → dotllm trust → dotllm init — and only then spawns the tmux session with cwd already set to the worktree (tmux new-session -c "$WT"). codex comes up already inside $WT. assert_worktree_ready "$WT" aborts loudly on any reordering; scripts/test-launch-order.sh pins it.
- Per-run session: each launch gets its own
tfmux-<slug> session, so runs never collide — fire as many as you like, each pings back on its own. Override with SF_TFMUX_SESSION.
- Launch config: the codex command lives near the top of
scripts/codex-go.sh (CONFIG POINT). Default is codexy (xhigh). Override per-run with --effort, or wholesale with SF_CODEXGO_CMD / SF_CODEXGO_EFFORT.
- Verified delivery: after
tfmux send the script confirms codex actually started and retries up to 5× — guards a readiness race that could leave the loop unstarted.
- Cleanup split: the launcher prints the exact
factory-cleanup.sh --session <NAME> --worktree <WT> --branch <BRANCH> --repo <REPO> --pr <PR_URL_OR_NUMBER> command because it knows the run handles. The bundled factory-cleanup.sh helper (in the tmux-factory-codex-go skill, fake-binary tested) owns the safety checks and deletion sequence — verify merged PR, ff-only update main, prove the merge commit is on main, remove the worktree, then kill tmux. Do not duplicate that logic in skill prose.
.llm is for codex, not routing: dotllm init gives the worktree a project .llm for $sf-auto's scratch; the ping is routed by the named tfmux session, not the .llm.
- Preview:
"$eng" --dry-run "<feature>" prints the exact steps (including whether ping-back is on) without spawning.
- Slug + sessions: slug derived from the feature (kebab, ~6 words) unless
--slug; tmux session = sf_<slug_with_underscores>_codex, tfmux session = tfmux-<slug>.
Crib sheet
"$eng" "<feature>" — fire it. --task-file · --slug · --effort low|medium|high|xhigh · --dry-run.
- Ping-back: codex
tfmux send mediator's a ✅ <slug> merged: <PR url> (or ⚠️ blocked) line to your pane — stay idle (needs you in tmux).
- After the ✅ ping (always finish it): verify
gh pr list --head feat/<slug> --state merged → run the printed factory-cleanup.sh ... --pr <PR> (syncs main + removes worktree + kills session). Not merged / ⚠️ blocked → surface it, don't clean up blind.
gh pr list --head feat/<slug> --state all · git ls-remote origin -h refs/heads/main — check authoritatively.
factory-cleanup.sh --session sf_<slug>_codex --worktree <WT> --branch feat/<slug> --repo <REPO> --pr <PR_URL_OR_NUMBER> — safe cleanup after merge; use the printed command.
tmux attach -t sf_<slug>_codex — watch live if needed.
bash -n scripts/codex-go.sh && bash -n scripts/factory-cleanup.sh · scripts/test-factory-cleanup.sh · scripts/test-dotllm-quarantine.sh · scripts/test-launch-order.sh · "$eng" --dry-run --slug smoke "smoke feature" — local checks after edits.