---
cycle: 380
type: alpha-closeout
date: "2026-05-19"
author: alpha@cdd.cnos
issue: "https://github.com/usurobor/cnos/issues/380"
verdict: APPROVED (R2)
rounds: 2
merge_sha: "770ea1b4"
base_sha_origin_main: "319893a4ea9de1b89989ef6e6dc44cd3e1cad147"
head_sha_cycle_at_merge: "4a7cda79"
last_alpha_implementation_sha: "87aa69e9"
---

# α Close-out — #380

## Summary

Added `cn activate --claude` / `cn activate --codex` flags that replace the cn process with `claude "$prompt"` / `codex "$prompt"` via `syscall.Exec` after rendering the 3.78.0 activation prompt. Default no-flag path preserved bytes-equal to 3.78.0 (verified by cross-version smoke against `cafabc8b`; 1151-byte output identical by SHA). Flags are mutually exclusive and report missing-binary errors pre-render, both proven by the "nonexistent HUB_DIR" oracle shape that asserts the absence of any render-time diagnostic when the pre-render check fires.

Surface containment: `spawn.go` (NEW, 70L, `//go:build unix`), `spawn_other.go` (NEW, 19L, build-tag stub for non-unix), `spawn_test.go` (NEW, 7 tests), `cmd_activate.go` (M, +47/-5), `cmd_activate_test.go` (NEW, 9 tests). γ scaffold named three target files; α added two declared widenings (`spawn_other.go` portability stub pre-validated by γ's risk register; `cmd_activate_test.go` required to avoid cyclic import between `cli` and `activate`). `activate.go` and `agent/activate/SKILL.md` byte-identical to 3.78.0 — render-capture seam Option A chosen and declared in `self-coherence.md` §Gap and §ACs AC1.

Two rounds: R1 REQUEST CHANGES with one B-severity honest-claim finding (F1 — §CDD Trace SHAs unreachable post-rebase); R2 APPROVED after a single self-coherence-only fix commit (`91a2cad6`) plus fix-round-1 append (`d838e7e9`). No production-code delta in the R1→R2 transition. Merge at `770ea1b4` on `main` (`Closes #380`).

## Implementation observations

- **Render-capture seam Option A chosen on the minimum-diff vector.** γ delegated the seam choice (capture buffer at call site in `cmd_activate.go` vs Render-only entrypoint in `activate.go`). α picked Option A: spawn arm in `cmd_activate.go:111-129` captures `activate.Run` stdout into a `bytes.Buffer`; default arm passes `inv.Stdout` straight through with no buffer indirection. AC3 bytes-equal property holds *by construction* (no buffer in the default path), not by accident. `git diff cafabc8b..HEAD -- src/go/internal/activate/activate.go` is empty — γ's pre-flagged failure mode #6 (silent renderer change) inoculated by α's explicit declaration in `self-coherence.md` §Gap, not by review-time grep.
- **Tests assert production exec shape via injection hook, not real binaries.** `spawn.go` exposes unexported package-level `defaultExecFn` (`syscall.Exec`) and `defaultLookPath` (`exec.LookPath`); test seam `spawnWith(execFn, lookPathFn, binary, prompt)` substitutes recording functions. `TestSpawn_DefaultsAreWired` swaps `defaultLookPath` for a sentinel observer to confirm the production path routes through `exec.LookPath`. Tests use `t.Setenv("PATH", t.TempDir())` to guarantee `LookPath` failure regardless of operator environment. γ's pre-flagged failure mode #1 (tests check flag parsing but not exec shape) does not apply.
- **`TestSpawnWith_CodexArgvShape` asserts both shape AND critical negative.** Test asserts `len(argv) == 2` AND `argv[0] == "codex"` AND `argv[1] == prompt` AND `argv[1] != "exec"`. The fourth clause explicitly disproves the `codex exec PROMPT` (non-interactive subcommand) failure mode pre-flagged by γ — if `Spawn` had wired `["codex", "exec", prompt]`, this test would fail loudly. The negative assertion lives in the test, not just the implementation.
- **Pre-render ordering proven by the "nonexistent HUB_DIR" trick.** `TestActivate_MutualExclusion_FiresBeforeRender` and `TestActivate_MissingBinary_FiresBeforeRender` both set `HUB_DIR` to a nonexistent path and assert stderr contains the pre-render diagnostic AND does NOT contain `Hub path not found` AND does NOT contain `Generating activation prompt`. If the pre-render check had been wired after `activate.Run`, the renderer's own error would have surfaced first and the test would fail by the absence of the pre-render diagnostic in stderr. The ordering is proven by what is *not* present in the output, not by a comment about which check runs first.
- **Non-unix portability stub stays inside the issue's non-goal envelope.** `spawn.go` is `//go:build unix`; `spawn_other.go` returns `<flag> spawn is not supported on this platform — use the default stdout form …` for `CheckSpawnBinary` on non-unix. The no-flag default path remains functional everywhere. The issue's §Non-goals excludes "macOS/Windows-specific spawning quirks beyond what falls out of syscall.Exec portability"; the stub is what falls out for free. γ scaffold §Risk register pre-validated the stub-or-build-tag option.
- **Missing-binary error names binary AND flag in one actionable line.** `spawn.go:38-46`: `✗ <binary> (requested by <flag>) not found in PATH — install it or ensure $PATH includes its directory`. Names the missing binary, the requesting flag, the PATH variable, and an install hint. AC5's "actionable" bar is met; γ pre-flagged failure mode #5 (binary OR flag, not both) inoculated by carrying both in the message text and asserting on both substrings in `TestActivate_MissingBinary_NamesBinaryAndFlag_{Claude,Codex}`.
- **`cmd_activate_test.go` widening required to avoid cyclic import.** AC3/AC4/AC5 oracles target `cmd_activate.ActivateCmd.Run`, which lives in `package cli`. Putting these tests in `spawn_test.go` (package `activate`) creates a cyclic import (`cli` already imports `activate`). The alternative — running the oracles only via `go run cmd/cn ...` shell smoke — is less reliable than a Go test. α declared the widening in `self-coherence.md` §Self-check rather than landing it silently; β accepted in R1 with no finding.

## Friction log

- **F1 root cause: path (a) email-rewrite rebase invalidates SHA citations authored before the rebase.** The cycle worktree at α-session start carried `gamma@cdd.cnos` in `.git/config.worktree` (inherited from γ scaffold session — same class as #379's friction log item 1 and #370 F4). α corrected via `git config --worktree user.email "alpha@cdd.cnos"` and applied `alpha/SKILL.md` §2.6 row-14 path (a): `git rebase --exec 'git commit --amend --reset-author --no-edit' 7a9bc2e7 && git push --force-with-lease origin cycle/380`. The rebase rewrote every α commit's SHA. §CDD Trace step table had already been authored citing the pre-rebase SHAs; after the rebase, the cited SHAs lived only in α's local reflog — not reachable from `origin/cycle/380`. β R1 F1 surfaced via review/SKILL.md rule 3.13(a) reproducibility: a future reviewer cannot resolve the pointers. Class: row-14 path-(a) timing vs SHA-citation authoring order. The row-14 prescription does not name the SHA-citation invalidation as a consequence; this cycle is the first surface where the two interacted.
- **Intra-doc repetition rule (α/SKILL.md §2.3) caught three β-blindspot occurrences.** β R1 F1 named 5 SHAs in §CDD Trace step table + 1 header reference in §ACs (6 occurrences). α grepped the file for every occurrence of the six pre-rebase SHAs before fixing and found 9 hits total — 3 of which β had not enumerated by name (one in §Debt, one in §CDD Trace step 6 prose, one in the §CDD Trace step 6 self-coherence-family start citation). Fixed all 9 in one commit (`91a2cad6`); R2 surfaced no F1-bis. The discipline derives from #266 F3-bis (β named one site; doc carried four occurrences; fixing only the named site surfaced F3-bis in the next round). This cycle is the second surface confirming the rule transfers to honest-claim SHA citations as a peer class to numeric/named-value drift.
- **β's R1 fix instruction was authoritative for the named sites but not exhaustive — α applied the intra-doc rule unprompted.** β's R1 finding evidence column gave the actual current-branch SHAs for the named sites (1cc80565 / d4f4e499 / e92a9476 / da3dd429 / 87aa69e9 — five values for five step rows). α applied the rule and grepped for all six unreachable SHAs, finding three more occurrences plus the §CDD Trace step 6 family start (`14a6bf55..` → `808ae8b7..`) that needed a sixth current-branch SHA. The fix-round 1 append in `self-coherence.md` lists all 9 sites in a table with β's verification recipe — the artifact thread carries the audit trail rather than scattering across commit messages.
- **Inherited project-state CI red did not block the cycle.** Two CI failures inherited from prior cycles: (a) `Repo link validation (I4)` failing on file:// links in `.cdd/releases/docs/2026-05-17/369/self-coherence.md` (cycle #369 artifact paths); (b) `R5-activate` kata P10 read-first ordering failure (introduced by #379's `activate.go` refactor; α's Option A choice keeps `activate.go` byte-identical, so the merge tree is bytes-identical to bare main on P10's failure shape). β disposed both per review/SKILL.md 3.10 judgment-not-strict-reading: inherited project-state CI red is not RC for a cycle whose diff neither introduces nor includes scope to fix the failure. β re-ran both validators against the merge tree and against bare `origin/main` and confirmed identical-shape failure on both — non-destructive merge with respect to the failing validators. Recorded for γ PRA chaining.
- **Worktree config leak class confirmed across three consecutive cycles.** #379 friction item 1, #370 F4, this cycle's F1 root cause — all three are the same class: `extensions.worktreeConfig=true` is enabled at the shared `.git/config` layer; sibling worktrees carry per-worktree `config.worktree`; a non-`--worktree` `git config user.email` write either lands in the shared layer (visible across worktrees) or is overwritten by a sibling-worktree process. β surfaced the same class in this cycle's merge-test recipe (β-closeout §Process observations §"Identity hygiene") via the same row-1 worktree-scope re-assertion. Three roles, three cycles, one repo-level surface. The class is detectable by `git config --get extensions.worktreeConfig` returning `true` → identity writes must carry `--worktree` from the first command.

## Patterns observed

**P1 — Row-14 path (a) rebase invalidates SHA-citation artifacts.** When `alpha/SKILL.md` §2.6 row 14 path (a) is exercised (`git rebase --exec 'git commit --amend --reset-author --no-edit' && git push --force-with-lease`), every α commit's SHA changes. §CDD Trace step tables, §ACs header references, and §Debt "at HEAD `X`" citations authored before the rebase all reference unreachable SHAs after the rebase. The row-14 prescription names the rewrite mechanically but does not name the SHA-citation invalidation as a downstream consequence. Two coherent resolution paths: (i) do the row-14 path-(a) rebase *before* authoring any SHA-bearing artifact (the identity-truth check moves to session-start, before the first α commit, so the rebase has nothing to rewrite); (ii) re-stamp SHA citations in `self-coherence.md` after the rebase as a mandatory post-rebase fix-up step. The intra-doc repetition rule (§2.3) catches the citations after the fact but does not prevent the class. Class signature: a mechanical row in the pre-review gate has a downstream invariant (artifact SHA reachability) that is not named in the row's own evidence.

**P2 — Render-capture seam Option A makes the bytes-equal property structural, not merely tested.** When the operator-visible invariant is "default path output unchanged", two implementation shapes satisfy the test oracle but only one satisfies the property by construction: (a) capture into a buffer always, write the buffer to stdout in the default arm — the bytes-equal property holds *if and only if* the buffer-to-stdout copy is byte-faithful (a test oracle catches divergence, but the shape admits divergence); (b) pass `inv.Stdout` straight through in the default arm, buffer only in the spawn arm — the default arm is bytes-identical to the prior-version default arm by construction (the buffer is not on the default code path). Option A is shape (b). The structural property is stronger than the test oracle: even if the unit oracle were absent, shape (b) cannot regress AC3 unless `activate.Run` itself is modified. γ's pre-flagged failure mode #6 (silent renderer change) is inoculated by Option A's structural shape, not by a test. The pattern generalizes: when a "preserve X exactly" invariant has a "minimum surface change" implementation and a "uniform surface change" implementation, the minimum-surface implementation moves the invariant from oracle-checked to structurally-preserved.

**P3 — Pre-render-ordering proven by absence-of-render-diagnostic, not by code reading.** The "nonexistent HUB_DIR" oracle shape (`TestActivate_MutualExclusion_FiresBeforeRender`, `TestActivate_MissingBinary_FiresBeforeRender`) proves pre-render ordering by what stderr does *not* contain. If the pre-render check ran after `activate.Run`, stderr would carry `Hub path not found` from the renderer's own error path. By setting HUB_DIR to a nonexistent path and asserting the pre-render diagnostic appears AND `Hub path not found` does NOT AND `Generating activation prompt` does NOT, the test proves the ordering empirically rather than by reading the code's line numbers. The pattern generalizes: when AC invariants are "X happens before Y", the load-bearing oracle is one where Y is wired to produce a recognizable side-effect on a degraded input, and the test asserts X's effect plus Y's effect's absence. Same class as #283 R1 F4 (smoke header asserting authority by absence-of-other-headers) and the "must surface" test discipline named in `alpha/SKILL.md` §2.6 row 9 polyglot re-audit.

**P4 — γ-scaffold failure-modes register operates as α-side authoring checklist (third cycle confirmation).** This cycle's γ scaffold pre-flagged six failure modes by number; α held each as a generation constraint during authoring (#1 → syscall.Exec primitive, not `os/exec.Cmd`; #2 → `argv[1] != "exec"` test assertion; #4 + #6 → nonexistent-HUB_DIR pre-render-ordering tests; #5 → error message names both binary AND flag; #7 → bytes-equal default arm via Option A). Same pattern named in #367's α close-out O3 and #379's α close-out P1 and #370's α close-out O6. The third repo-level confirmation is that this is structural to triadic cycles where γ's authoring discipline includes a failure-mode catalogue, not cycle-specific. The cycle's round count (2, target ≤2) and finding density (1 B-severity, 0 D-severity) reflect the discipline.

**P5 — Worktree-config identity-leak class is repo-level (three cycles, three roles).** #379 friction item 1 (α surface), #370 F4 (α surface), this cycle F1 (α surface), this cycle β-closeout §"Identity hygiene" (β surface — preventive `--worktree` flag from session start, not from recovery commit). All four share one root: `extensions.worktreeConfig=true` at the shared `.git/config` layer + non-`--worktree` writes overwritable by sibling-worktree processes. The class is mechanically detectable: `git config --get extensions.worktreeConfig` returns `true` → identity writes must carry `--worktree`. The repeat across three consecutive cycles in three role surfaces indicates the class is repo-level rather than role-specific. `alpha/SKILL.md` §2.6 row 14 describes the retroactive fix (path a) but does not prescribe the preventive write at session start; β's R1 row 1 prescribes `--worktree` for merge-test scope but is similarly retroactive in shape.

## CDD Trace evidence

- **Issue:** `https://github.com/usurobor/cnos/issues/380`
- **γ scaffold:** `.cdd/unreleased/380/gamma-scaffold.md` (commit `7a9bc2e7`)
- **Base SHA:** `319893a4ea9de1b89989ef6e6dc44cd3e1cad147` (origin/main at dispatch)
- **α implementation commits** (post row-14 path-(a) rebase, all `alpha@cdd.cnos`):
  - `1cc80565` step 1 — recognize --claude / --codex flags in cn activate
  - `d4f4e499` step 2 — add activate.Spawn / CheckSpawnBinary helper
  - `e92a9476` step 3 — wire --claude / --codex mutual exclusion + spawn call
  - `da3dd429` step 4 — tests for spawn helpers + cn activate flag flow
  - `87aa69e9` step 5 — AC3 bytes-equal default-path oracle
- **α self-coherence commits** (incremental per `alpha/SKILL.md` §2.5):
  - `808ae8b7` §Gap
  - `73c1c0ef` §Skills
  - `ab48386f` §ACs
  - `0c7ec114` §Self-check
  - `32689a14` §Debt
  - `1f579264` §CDD Trace
  - `69b9ea9d` §Review-readiness — R1 signal
- **β R1 verdict:** `2dac48ba` REQUEST CHANGES — one B-severity honest-claim finding F1 (§CDD Trace SHAs unreachable post-rebase)
- **α R1→R2 fix:**
  - `91a2cad6` rewrite §CDD Trace SHAs to current-branch-reachable values (9 occurrences across 3 sections, intra-doc repetition rule applied)
  - `d838e7e9` self-coherence §Fix-round 1 append (forensic table + β verification recipe)
- **β R2 verdict:** `4a7cda79` APPROVED — F1 closed, all ACs pass, no new findings
- **Merge:** `770ea1b4` on `main` (non-fast-forward; `α #380 cn activate --claude / --codex flags — spawn AI body interactively`, `Closes #380`)
- **β close-out:** `b53ba6a4` (β post-merge close-out on `main`)
- **Pre-review gate (14 rows) evidence:** `self-coherence.md` §Review-readiness (R1) and §Fix-round 1 re-audit-against-HEAD table (post-F1)
- **AC-by-AC evidence:** `self-coherence.md` §ACs (AC1–AC5; per-AC oracle output pasted from `go test -v` runner; β R2 re-verification in `beta-review.md` §"AC Coverage (R2)")
- **β honest-claim verification:** `beta-review.md` §"Honest-claim verification (rule 3.13) R2" — all four sub-checks ✓ at R2
- **Surface diff at merge:** `git diff origin/main..origin/cycle/380` lists 8 files: `gamma-scaffold.md`, `self-coherence.md`, `beta-review.md`, `spawn.go`, `spawn_other.go`, `spawn_test.go`, `cmd_activate.go`, `cmd_activate_test.go`

No undeclared debt. The two items in `self-coherence.md` §Debt named at α-side authoring time (manual interactive REPL smoke is δ/operator scope; non-unix platforms emit unsupported-platform error on `--claude` / `--codex` by design) plus the four issue-named deferrals (`--auto`, `$CN_DEFAULT_BODY`, `--cursor` / `--aider`, README router patch) are the complete debt set. β added no debt items of its own. Two project-level CI inheritances (#369 link validation, #379 R5-activate P10) are recorded for γ PRA chaining; neither introduced by this cycle.
