Mac Mini delegation¶
Since 2026-09-24 the orchestrator (claude-3 on the operator's MacBook) delegates heavy Claude work to headless Claude
Code on the Mac Mini. The operator's words: "from now on use claude on the Mac mini remote since it has more tokens",
and on 2026-09-25, "You orchestrate and mac mini does the work". The standing exception, and the rule never to touch
the Mini's OAuth token, are in CLAUDE.md. The SSH and fence basics are in
hosts.md.
The Mini writes. The orchestrator verifies and publishes. The Mini's gh identity is ibuild-e-bot, which cannot
read rig repos, and rig agents do not work on the rig. So code moves by git over SSH, and every commit is re-checked,
re-authored and pushed from the MacBook.
What runs where¶
The MacBook account and the Mini's account have separate usage limits. On 2026-09-27 the MacBook account had used 94% of its weekly limit. The largest single spend was a local adversarial-review Workflow: 7 agents, about 700k tokens.
| Work | Where |
|---|---|
Adversarial review: review, refute each finding, write findings.md |
Mini job, opus-5-5 xhigh |
| An investigation that needs more than about three files | Mini job that returns a short verdict with file:line |
Implementation, tests and full suites (~/bin/verify-stack.sh) |
Mini job |
| PR content beyond a few lines | Mini job (medium for docs written from given facts) |
| Planning, the final patch check, publish and merge, rig scripts that print counts rather than dumps | Orchestrator |
| Anything gated by the auto-mode classifier or the credential policy | Orchestrator, with the operator |
| App features, and every review and merge of a dashecorp PR | The rig: Dev-E, Review-E, MergeGate |
The rig never does rig-internal work, and Dev-E cannot push workflow files (infra#457). Those go to the Mini or the orchestrator.
One job = one fresh session¶
Each task is a directory ~/jobs/<name>/ on the Mini:
| File | Content |
|---|---|
dir |
working directory (a clone or worktree under ~/repos/) |
model |
e.g. claude-opus-5-5 |
effort |
low / medium / high / xhigh / max, always set explicitly (the CLI default is undocumented) |
tools |
the --allowedTools entries, one per line |
prompt.md |
the brief |
out.json, err, progress.md |
written by the run |
Run it from the MacBook with run_in_background, so the session is notified when it finishes:
A job whose brief runs the Api suite (Testcontainers) runs inside
~/bin/with-rigbuild.sh. The wrapper starts the rigbuild VM, holds its
lock for the whole job, fails closed unless the engine is rigbuild, and stops the VM afterwards (the
review's 2026-10-20 amendment):
ssh -i ~/.ssh/mac-executor claude@100.92.170.124 'zsh -ic "~/bin/with-rigbuild.sh ~/bin/run-job.sh <name>"'
- Tools: keep
Bash(dotnet test:*), and give nocolima,dockeror OrbStack entry.run-job.shrefuses (exit 3) a tools file with a Bash rule that names colima, docker, lima, limactl, OrbStack,orbororbctl, grants all of Bash, starts with a wildcard such asBash(*:*), or starts with a launcher such as a shell,env,xargs,sudo,open,source,., the wrapper and job scripts (with-rigbuild.sh,run-job.sh,lockf) or an interpreter with a code flag (python3 -Ic,node --eval), by name or by full path. A refusal prints only the file and line, never the rule text. The CLI splits each tools line on commas and spaces, so a rule anywhere on a line counts (Read, Bash(docker ps:*)is refused). The job directory's.claude/settings.json, which--setting-sources projectloads, gets the same check; its hooks are not checked. It matches names only:Bash(git:*)(an alias starting with!),Bash(dotnet:*)anddotnet testitself (MSBuild targets, test code) can still run any command. So read the tools file yourself as well. Older jobs' tools files still carry colima and docker rules, so never copy one forward. Whileclaudehas passwordless sudo on the Mini (operator-inbox#51), these checks are a speed bump against mistakes, not a boundary against a hostile job, because any job can become root. - Brief: "rigbuild is up for the whole job; never start or stop it; if Testcontainers reports Docker unavailable, stop and report."
- Exit codes (the log is
~/.rigbuild/with-rigbuild.log): - 75: another wrapped run holds the VM. Launch again when it finishes.
- 98: the job did not run. Either the engine check failed, or the wrapper refused before starting anything:
env.shdid not source,~/.testcontainers.propertiesexists, or rigbuild was already up outside the lock (a run whoselockfwas killed,~/bin/flake-check.sh, a manual start). In that last case, find out what uses the VM before anyone runscolima stop --profile rigbuild. - 96 and 97: findings after the run (a new image, or a listener not on loopback). 95:
colima stopfailed, so rigbuild may still be running, or the run'slockfwas killed and another run holds the lock, so the stop was left to that run. A finding's code is returned even when the command failed; the log lists them all. - 99:
RIGBUILD_LOCKEDwas not set by the wrapper's ownlockf. - From
run-job.sh: 3, the tools file (or the job directory's.claude/settings.json) was refused; 4, the job is outside a live wrapper while an engine answers on/var/run/docker.sock, or nodockerCLI is on PATH to check that, or~/.testcontainers.propertiesexists. - 73:
lockfcannot open~/.rigbuild/vm.lockbecause~/.rigbuildis missing. Nothing ran. - 70:
lockfreports that the wrapper's body was killed by a signal. If this run'sstart:line in the log has noend:line after it, the body died before the stop and rigbuild may still be up (the next run refuses with 98). Otherwise it died after the stop, for example by SIGPIPE on its final print after an ssh drop, and thatend: rc=line has the real code. - A command that itself exits 75 or 95 to 98 returns the same code. The log tells them apart: a run refused with
75 by the lock writes no
start:line, and a command's own code shows in itsend: rc=line. - Jobs that don't run the Api suite are launched unwrapped.
run-job.shrefuses them (exit 4) while an engine answers on/var/run/docker.sock, so stop OrbStack's engine first (step 1 of the review's removal plan). It also refuses them when nodockerCLI is on PATH, since it then cannot check.env.shonly removes~/.orbstack/binfrom PATH, so the docker CLI comes from the launch's own PATH.
~/bin/run-job.sh (source) always passes --model, --effort,
--setting-sources project (without it, the host's Bash(*) user setting makes the allowlist meaningless, #947) and
the job's own write access to progress.md. It then appends one row to ~/jobs/ledger.tsv.
Giving a job its repo. The Mini's gh can't read rig repos. Since the ibuild-e-bot key rotation of 2026-10-14
(claude-3#1206), it answers HTTP 401: Bad credentials for every repo (checked 2026-10-18). So ship a bundle, build the
job's clone from it, and give the job no gh tools. This recipe worked for every job on 2026-10-14/15:
# MacBook: no checkout in the local clone
git -C <clone> fetch -q origin main && git -C <clone> bundle create -q x.bundle origin/main
# Mini, after scp to ~/jobs/
d=~/repos/<job-clone>; git init -q $d \
&& git -C $d fetch -q --update-head-ok ~/jobs/x.bundle 'refs/remotes/origin/main:refs/heads/main' \
&& git -C $d symbolic-ref HEAD refs/heads/main && git -C $d reset -q --hard refs/heads/main
Put inputs in ctx/ inside that clone and add ctx/ to .git/info/exclude. Collect the result over SSH with
git add -A -- <paths> && git diff --cached (add --binary for PDFs and images). The orchestrator then applies it,
commits and pushes.
- Don't
git clonethe bundle. It carries onlyrefs/remotes/origin/main, so the clone has nomaincommits and an empty checkout (review-1015, 2026-10-15). Use the fetch recipe above. A bundle of a local branch (git bundle create x.bundle main) carriesrefs/heads/mainand clones fine (jobs autodeploy and ad1, 2026-10-18). ssh -nthrows away a heredoc.ssh -n … 'bash -s' <<'EOF'reads stdin from/dev/null, so the remote script silently does nothing. Drop-nwhenever a script is piped in.- Ask for deliverables in
progress.mdtoo. Jobsite-issues(2026-10-15) had its writes toctx/refused at the permission prompt. Its relativeWrite(./ctx/**)rule stopped matching after acdmoved the session (see the absolute-path rule below). It saved both issue bodies inprogress.mdbetween<!-- BEGIN name -->and<!-- END name -->markers, and the orchestrator extracted them from there.
Every job gets ~/jobs/PREAMBLE.md as an appended system prompt:
- Identity: you are a delegated job, not iBuild-E. The Mini's
~/.claude/CLAUDE.mdis iBuild-E's persona ("You are iBuild-E … Discord"). It loads even under--setting-sources project. Never edit it, because the live service uses it. - Scope: never push, open PRs or post to Discord.
- Docs ride with behaviour, and
updated:is bumped. progress.md: keep it current, so a killed job can resume from it plus the git state.LEARNINGS: end every reply with this section. The orchestrator folds it into memory and docs.
Model and effort routing¶
Choose by the cost of a wrong answer and how mechanically the result can be checked, not by token cost. A TDD slice costs 1-6 minutes and about $1-2 counterfactual (the Mini runs on a flat subscription), so a cheaper model saves almost nothing.
| Task | Model | Effort |
|---|---|---|
| Design, root cause, anything that changes live behaviour or picks a rollout | opus-5-5 | xhigh (max if the first attempt was wrong) |
| Adversarial review of a design or a security review | opus-5-5 | xhigh |
| TDD implementation slice from a decided design | opus-5-5 | high |
| Mechanical work with a mechanical check (re-slicing with tree equality, docs from given facts, inventories) | opus-5-5 | medium |
| Bulk retrieval, many repeated runs, output fully re-checked | sonnet-5 | medium |
| Haiku | never for judgment | — |
Feedback loop: after verifying a job, the orchestrator records a verdict in the ledger (accepted as-is,
accepted + N fixes, rejected: why).
- A defect in the job's own work: the next job of that class moves up one effort step.
- Three clean accepts in a row: try one step down.
- A permission denial is a wasted turn. Fix it in the allowlist, not with a stronger model.
A pure build-and-test re-verification needs no model at all: verify-stack.sh
builds, runs the four guards and the full suites for a list of branches as a plain script. It runs itself inside
with-rigbuild.sh.
Context¶
- Every job is a new session. Nothing carries over except the brief,
progress.mdand git. - Size: the window is 1,000,000 tokens (
modelUsage.contextWindowforclaude-opus-5-5), and startup costs about 20k. Peaks measured on 2026-09-25 ranged from 20k (a probe) to 283k (an xhigh design review), with no compactions. - Ceiling: size a job to finish under about 250k. Past about 400k, let it finish its current slice, stop it, and
start a fresh job from
progress.md. - Monitoring:
python3 ~/bin/job-context.py [minutes](source) shows the current and peak context of live transcripts.job-facts.pygives turns and peak per job for the ledger and the orchestrator board.python3 ~/bin/audit-job.py <job>(source) audits what a finished job actually ran (see Verification). - Don't trust
out.jsonfor turns and time. Itsnum_turnsandduration_mscan cover only the last segment of a run. One 5-minute job with 112 transcript messages reported 9 turns and 37 s. Take turns and minutes from the transcript. - One test VM, one holder. There is one colima test VM,
rigbuild, andwith-rigbuild.shserializes it with a lock. A second wrapped job, or averify-stack.shrun, started while another holds it exits 75 (already locked) without touching the VM. One that finds rigbuild already up outside the lock exits 98, also without touching it.~/bin/flake-check.shon the Mini starts colima without the lock; never run it. - Stopping a job: stopping the local
sshtask does not stop the remote job. Kill the job'sclaudeordotnetprocess on the Mini:pkill -x claude, orkillwith its pid (frompgrep -x claude) when other jobs run. Never killlockf, and never usepkill -f:lockf's command line holds/Users/claude/…,run-jobandwith-rigbuild, sopkill -f claudekills it too. Withoutlockfthe lock is free while the job and the VM keep running. Once rigbuild answers, the next wrapped run finds it up and refuses with 98 instead of stopping it. Killed duringcolima start, before rigbuild answers, a second run can take the lock and start the VM too. If the killed run ends first, it leaves the stop to the other run (FINDING, 95); if the other run ends first, it stops the VM under the killed run's job.
Verification before anything is published¶
The orchestrator never publishes a job's claim unchecked:
- Audit the run with
python3 ~/bin/audit-job.py <job>(source). It readsout.jsonanddir(a relativediris taken from$HOME), walks the main transcript (~/.claude/projects/*/<session_id>.jsonl) and every subagent transcript (~/.claude/projects/*/<session_id>/subagents/*.jsonl), and prints: - the permission denials from
out.json; Agent uses: N, subagent transcripts: M, with aWARNINGwhen M < N, since a missing transcript is an unaudited subagent. N counts onlyAgent/Taskcalls classifiedran: a denied or failed call starts no subagent and leaves no transcript. Only.jsonlfiles holding messages count as transcripts;subagents/also holds other files;- each tool use other than Read/WebFetch/WebSearch/ToolSearch/TodoWrite, each Write/Edit target other than the
job's
progress.md, each Grep/Glob with itspathandpattern, and each Read outsidedir. A Grep, Glob or Read whose path resolves outsidediris flaggedOUTSIDE DIR.
Each call is classified in this order:
1. DENIED if its tool_use_id is in out.json's permission_denials (entries carry tool_name,
tool_input and tool_use_id).
2. Otherwise DENIED if it is an error whose text starts with a known denial opening, matched case-insensitively
after stripping whitespace and a <tool_use_error> wrapper: "This Bash command contains multiple operations",
"This command requires approval", "A variable in this command can't be checked", "Contains brace with quote
character", "Claude requested permissions", "A command used as a file in this command can't be checked before
it runs" or "Output redirection to" (that text ends "needs approval"). An error containing "was blocked. For
security" anywhere is also DENIED: that denial opens with the command name (ls … was blocked. For
security …). This covers subagent transcripts and older out.json files, whose permission_denials miss those
calls.
3. Otherwise RAN, FAILED for any other error: the call ran. A git push "denied by pre-receive hook" and a
Read's "File does not exist" both land here.
4. Otherwise ran.
Result excerpts are printed only for DENIED and RAN, FAILED. Before anything is printed or truncated, these
token shapes are replaced with [REDACTED]: gh[oprsu]_…, github_pat_…, sk-ant-…, xox[abpr]-…, AKIA…,
ya29.…, bearer … (any case), a PEM private key from its -----BEGIN … PRIVATE KEY----- line to its -----END
…----- line (or to the end of the excerpt), and any …TOKEN…=, …SECRET…=, …PASSWORD…= or …API_KEY…=
assignment (any case). Known token shapes are redacted; skim before pasting. Transcripts are read as UTF-8 with
bad bytes replaced, and an unreadable transcript is reported as CANNOT READ and skipped, so one bad file does not
stop the audit. --jobs-dir and --projects-dir (defaults ~/jobs, ~/.claude/projects) point it at a copy. To
test changes to the script, build a throwaway fixture there (a denial listed in permission_denials, a
ran-but-failed command, a Grep and a Read outside dir, more Agent uses than subagent transcripts, a denied
Agent call, a fake token in a command). Don't commit it: token-shaped strings trip GitHub push protection.
Denied calls are fine. Anything that ran outside the fence is not; see
the gotchas for what Claude Code lets through on its own.
2. Re-run the tests on the reference machine:
- rig-conductor: the full suites on the Mini's rigbuild VM, comparing trx executed with total;
- rig-agent-runtime: npm test on the MacBook, which has the node_modules the Mini must not install.
3. Reproduce the mutation proof independently.
4. Build after every rebase. A clean git merge-tree is not a compiling tree: an rc#2375 rebase merged cleanly and
did not compile.
5. Publish a verified stack by patch, not by tree. Before each push, assert that each slice's git diff against its
parent equals the verified reference.
6. Check any claim the model made from memory. For example, codex's reasoning-effort values were read from the codex
binary rather than trusted from the job.
- Check a deletion against the target repo's recorded decisions. Job
nutri-retire(2026-09-29) deleted all of dashe-nutri-e'swebsite/, which that repo'sCLAUDE.mdandwebsite/RETIRED.mdkeep as the record. The orchestrator published the smaller change the inventory had planned instead (dashe-nutri-e#60). Before briefing a delete, grep the target'sCLAUDE.mdand READMEs for what they say to keep, and put that in the brief. - See a new check fail before publishing it. A job that may only run its own check can show it passing, never failing. Copy the repo, apply one mutation at a time (broken JSON-LD, a dead link, a bad redirect status, an unclosed tag), and require exit 1 for each. The PR checks of invotek-no#33 (6 of 6 caught) and stigjohnny-no#24 (9 of 9) were proved this way.
- Give review jobs facts, not recycled brief text.
- Build the review diff with
git diff $(git merge-base origin/main HEAD) HEAD.gh pr diff, run right after a push, served the previous head. - Let the reviewer run the code on real data, with the exact repo-relative rule
Bash(python3 scripts/…:*). The same command with an absolute path is denied. - Re-check every number in the brief. A wrong count, copied from one brief to the next, cost #1160 two extra review rounds.
- Give the latest published artifact as the baseline, not only a diff against an old base. A final CV gate (2026-10-15) failed the print because two skills "changed" to fading. The diff was against the November 2023 repo, and the operator's May 2026 print, the real baseline, already had them fading.
- Put every file the site links to in scope, PDFs included. The 17-page whitepaper that stigjohnny.no linked held most of the factual errors, and only reviewers who went beyond the brief's list found it.
- Merge the reviewers' fixes; don't stack them. Two reviewers' wording, applied together, repeated "Agentic engineering" two lines apart. Check pasted reviewer text against the operator's latest decisions too: one review credited the apps to Dashecorp, which the App Store listing contradicts.
- Verify exact-string Dev-E issues before filing. Dev-E matches each
beforetext literally. Runverify-issue.py<issue.md> <clone at current main>right beforegh issue create, and again ifmainmoved. Two PRs landed between drafting and filing stigjohnny-no#31, and the draft had to be updated.- When you watch for Dev-E's PR, match
Closes #N(or Fixes or Resolves), not a bare#N. On 2026-10-16 a watcher for stigjohnny-no#33 matched old PR #24, whose body mentions "invotek-no #33", and ran its live check before the real PR existed.
- When you watch for Dev-E's PR, match
-
Review public copy before it goes out. Run three independent read-only reviews of the exact texts and images, one lens each:
- the buyer;
- language and platform craft;
- facts and risk: claims, promises, confidentiality and marketing law.
Put the texts in
ctx/review-pack.md(effort high, about 5–8 minutes in parallel), then merge the fixes and show the operator the result. The 2026-10-16 round (rev1016-*) caught three errors in already-approved posts: - apps credited to the wrong company; - "code review" when an AI agent does it; - examples traceable to a confidential prospect's prototype.The whole pipeline can run on the Mini, as it did on 2026-10-20 for a one-page offer and an invotek.no section: 1. A drafting job (effort xhigh) writes from a context pack: the live texts plus a binding
constraints.md. 2. The three reviews run in parallel on the draft (high, about 3 minutes). 3. A merge job (xhigh) reads the reviews and the live texts, merges the fixes, and lists the operator's decisions with defaults.To check claims against a public page, list only that domain in the merge job's tools file, e.g.
WebFetch(domain:apps.apple.com). WebFetch quotes at most about 125 characters, so it checks subtitles and short quotes, not whole descriptions. That round also caught live-site copy that contradicted the new offer (an unmeasured "one consultant does a whole team's work" claim, and the internal agent platform offered to clients), and «Timer» (Norwegian for "hours") where «Tidtaker» was meant.
Job results last on the Mini, not on the MacBook. ~/jobs/<job>/out.json (its result field) keeps every job's
output; the 2026-10-16 jobs were still there on 2026-10-22. The MacBook scratchpad doesn't last: its result.md
copies, renders and helper scripts from 2026-10-16 were gone by 2026-10-22. Point memory at the Mini copy, and put
anything that must last in GitHub (an issue, a repo doc).
A job's clock times are not evidence. A job writes times into progress.md that it never read from a clock. Job
h12 (2026-10-19) ran from 09:38:35Z for 22 minutes (its ledger row), which is 11:38 to 12:00 on the Mini's clock, yet its
log reads 11:50 to 13:50. Take times from the ledger, out.json or GitHub, never from a job's log. Brief jobs to write
no clock time they did not read with a tool.
Allowlist gotchas (headless -p mode)¶
- The fence is not "allowlist only". Even under
--setting-sources project, Claude Code auto-approves its own read-only Bash commands and theAgentandToolSearchtools, and lets a session read its owntool-results/. Subagents inherit the allowlist, so audit their transcripts too (~/.claude/projects/*/<session>/subagents/*.jsonl;audit-job.pydoes). Seen in jobinvotek-review(2026-10-14, allowlistRead(./**),WebFetch,WebSearch, claude-3#1159): - ran:
ToolSearch;Agent, which started 5 subagents;sleep 240in the background;grep -n -i …on its own tool-results file;printf '…' | LC_ALL=en_US.UTF-8 wc -m; aReadof its owntool-results/*.pdf, outside./**; - denied:
curl,awk,&&/;chains, commands with$var, a heredocpython3, andcd /tmp && mkdir. A plain pipe of read-only commands (printf … | wc -m) ran.
Nothing harmful ran, but don't brief a job as if it can only call what is listed.
- Bash(bash scripts/check-:*) never matches: a prefix rule must end at a word boundary. List each guard exactly, e.g.
Bash(bash scripts/check-tenant-scope-guard.sh).
- Compound commands (a && b), $(…), loops, cd, git -C, redirects to /tmp and rm all need approval, which
means they are denied. Brief jobs to run single commands, write test results inside the repo
(--results-directory TestResults-<job>), and use the Write/Edit tools for scratch files.
- Bash(git diff:*) is not a write fence: git diff --output=<path> writes any file. Grant exact command forms
instead of git diff:*.
- git checkout -- <file>, git range-diff, git check-ignore and mkdir also need approval, so they are denied
headless.
- An unquoted ; inside an argument (e.g. -clp:ErrorsOnly;Summary) reads as a second command and is denied. Quote
it.
- WebFetch is not granted unless the tools file lists it, so jobs cannot fetch BRAIN.md. Put what they need in the
brief. A domain-scoped entry such as WebFetch(domain:apps.apple.com) works (job kart-merge, 2026-10-20).
- In dashe-cutie (2026-09-29, job cutie-gate), the allowlist that worked in infra let the job edit
.github/workflows/*.yml but denied its edits to *.md files and its git commands, so it could not commit. Brief
such jobs to write the doc text into progress.md and leave the edits uncommitted. The orchestrator then applies the
patch and the text.
- node --test <file> runs without prompting. Test files that import ioredis etc. do not load without node_modules,
so logic under test belongs in pure, dependency-free modules (the rar#744 and rar#747 pattern).
- In claude-3, every Edit or Write under
.claude/**is denied headless, even withEdit(./**)(jobc3-nutrieapp, 2026-09-29). Skill edits are the orchestrator's. - Use absolute paths in Read, Edit and Write rules:
Edit(//Users/claude/repos/<clone>/k8s/**). A relative./rule is matched against the job's current directory. In jobdell-fix(2026-10-14) the first writes passed, then every write was denied once the directory moved, and half the work was lost. The rerun with absolute rules wrote everything. - A job can read its own job directory only through an absolute rule, such as
Read(//Users/claude/jobs/<job>/**); jobdell-622read its evidence bundle that way. Without one it can only writeprogress.mdthere. The older workaround still works: put the input in the repo as an untracked file listed in.git/info/exclude(jobnutri-retire). - The orchestrator runs
tofu validateandtofu test. A job cannot run tofu in a module subdirectory, becausecd,-chdirand-Care all denied. Use a throwaway copy of the module with itsbackendblock removed and a plan-onlytofu test; that provesvalidationblocks, whichvalidateskips. - A job cannot delete or rename files. In job
w633-gitops(2026-10-14),rm,mv,git rm,git mv,git apply,git restoreandgit update-indexwere all denied. So the CronJob that the job replaced stayed tracked, and the orchestrator removed it after bundling the branch back. When a brief removes or renames files, grant exact forms such asBash(git rm apps/x/old.yaml), or leave that step to the orchestrator. - Check the output against repo budgets the job doesn't know about. dashecorp/infra's docs-check fails when
AGENTS.mdexceeds 8,192 bytes, and a one-line addition broke it (infra#637). - claude-3's project
SessionStarthook still fires under--setting-sources projectand tells the job to run/load-memory. The brief says to ignore it. - Headless Chrome hangs over SSH on the Mini.
--headless=new --print-to-pdfnever finishes, even with--use-mock-keychain --password-store=basic, because theclaudeuser has no GUI session (2026-10-15). - Render PDFs and screenshots on the MacBook:
"Google Chrome" --headless=new --no-pdf-header-footer --virtual-time-budget=8000 --print-to-pdf=out.pdf <url>. - Serve the folder (
python3 -m http.server) when pages use absolute paths such as/styles/…. - Send the page count and any layout problems back to the job.
The Codex CLI (2026-10-21)¶
The operator asked to "outsource" portal issues to Codex. The Codex CLI is installed for post on the Mac Mini
(~/.local/bin/codex, 0.160.1, already logged in), and the rig's Dev-E Codex lanes use the same tool.
- Run it in a fresh clone of the repo in the session scratchpad, on a feature branch:
codex exec -s workspace-write -o <last-message file> "<brief>", in the background. The brief holds the issue's text and the repo's rules: tests first, the docs to update, and the files it must not touch (wrangler.toml, migrations,.github/). - It can't commit: the
workspace-writesandbox makes.gitread-only. Tell it to leave its changes in the working tree. The orchestrator reviews the diff, commits with the session's attribution lines, pushes, and opens the PR, withneeds-reviewin dashecorp. - Its test run isn't the verdict: tests that open a localhost port fail inside the sandbox with
listen EPERM. Rerun the full suite outside the sandbox before committing. - Check the names it assumes against the code, such as audit event types. Some are built at runtime (for
example
`discussion.${kind}`), so a plain grep can miss them. - Delivered this way on 2026-10-21: dashecorp/invotek-portal #194, #198 and #200, each reviewed by the orchestrator and Review-E.
Rules that stay in force¶
- Never read, print, copy, export or rotate the Mini's
CLAUDE_CODE_OAUTH_TOKEN. Use it only throughzsh -ic. - Never touch the live iBuild-E config (
~/repos/claude-3/agent-runner/agents/ibuild-e.jsonhas a live local edit). Use a separate clone or worktree (~/repos/claude-3-944). - For launchd plists and scripts, collect sanitized metadata yourself (env var names only, long values masked, as in
launch-inventory.py) and give only that to a job.