Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions SESSION_LOG_2026_07_18.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Session log — 2026-07-18

## Objective

Merge and deploy the bounded multi-session lifecycle work, then complete the
caller-side Codex identity correction.

## Merge and deployment

- PR #367 merged to `main` as `67f9e1921b2b6d3f01fc3cd844d1edd8ad2a045e`
after all 12 protected checks passed.
- Canonical local gate `test-env/run.sh` passed immediately before merge.
- Installed the merged release binary at `~/.cargo/bin/wire` after candidate
and staged SHA-256 hashes matched. Preserved the previous binary at
`~/.cargo/bin/wire.pre-daemon-lifecycle-20260718` for rollback.
- Installed the missing launchd local-relay service. It initially remained in
`xpcproxy` under process pressure, then reached `exec` and accepted TCP after
the managed daemon restart reduced load.
- Restarted only `sh.slancha.wire.daemon` through launchd. No wildcard kill,
hand-started daemon, upgrade command, live-home rewrite, or destructive
cleanup occurred.

## Measurements

- Before rollout: 592 daemons, 4,692,192 KiB aggregate RSS, 41 MCP processes,
690 session homes, local relay refused connections.
- After managed restart: 60 daemons, about 760,240 KiB aggregate RSS, 41 MCP
processes, local relay accepting connections.
- Remaining workers comprise legacy MCP-owned children and capped/reparented
supervisor workers. Existing MCP processes loaded the prior binary and age
out with their owning Codex sessions.

## Follow-up root cause

The fixed Codex config override cannot yet be removed safely. Live Codex
exposes `CODEX_THREAD_ID` but not `CODEX_SESSION_ID`; Wire reads only the latter.
Without a new adapter, Wire would fall through to a minted or machine-default
identity instead of a stable per-thread identity.

GitNexus impact for `resolve_session_key` is LOW: four direct dependants, six
impacted symbols, and two affected runtime entry flows (`cli::run` and
`mcp::run`). Selected fix is documented in
`docs/superpowers/specs/2026-07-18-codex-thread-identity-design.md`.

## Codex thread adapter implementation

- Added `CODEX_THREAD_ID` immediately after the existing
`CODEX_SESSION_ID` compatibility adapter. Both resolve with source
`codex-cli`; explicit Wire and Claude Code overrides retain precedence.
- RED: the focused Codex adapter test failed with `CODEX_THREAD_ID` resolving
to `None` before the production change.
- GREEN: three session-key adapter tests passed. The isolated lifecycle,
655-stale-home supervisor, relay-unavailable, and bilateral local-sister
suites passed; local-sister dial converged without manual pull.
- `cargo fmt --all -- --check` and
`cargo clippy --all-targets --all-features -- -D warnings` passed.
- Added `CODEX_THREAD_ID` cleanup anywhere an isolated test process already
removed the older Codex variable. Every temporary `WIRE_HOME` spawn in this
scope retains `WIRE_HOME_FORCE=1`.

## Canonical gate

- First `test-env/run.sh` attempt passed 653 library tests, then Cargo could
not execute its just-built zero-test `src/bin/wire.rs` harness because that
artifact was absent from the shared Docker target volume. Source inspection
found no test invoking binary purge, no concurrent Wire test container, and
a Dockerfile note documenting Cargo 1.88 target-path loss on named volumes.
- Rebuilding that single bin-test target in the same container volume passed.
- A second full `test-env/run.sh` run passed end-to-end, including formatting,
clippy, all serial Cargo targets, release build, demos, and shell integration
checks.

## Deployment decision

Push and merge through protected checks before changing caller configuration.
Install the merged binary atomically, then remove only the fixed
`WIRE_SESSION_ID` assignment from Codex's app-managed configuration. Preserve
a mode/timestamp-retaining backup; do not touch shell dotfiles. Existing Codex
processes keep inherited identity until they exit; fresh processes use their
thread ID.
197 changes: 197 additions & 0 deletions docs/superpowers/plans/2026-07-18-codex-thread-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Codex Thread Identity Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Resolve fresh Codex processes from their stable `CODEX_THREAD_ID` so removing a shared `WIRE_SESSION_ID` restores independent Wire identities.

**Architecture:** Extend the existing ordered environment adapter in `resolve_session_key`; do not add another identity path. Keep `WIRE_SESSION_ID`, Claude Code, and `CODEX_SESSION_ID` precedence intact, reuse the `codex-cli` source label, and make every relevant test process clear the newly recognized host variable.

**Tech Stack:** Rust 2024, Cargo tests, shell CI mirror, launchd-managed local service, GitHub protected checks.

## Global Constraints

- Preserve live Wire homes and launchd ownership; do not hand-start daemons, run `wire upgrade`, kill process families, or rewrite live homes.
- Every spawned `wire` process using temporary `WIRE_HOME` must set `WIRE_HOME_FORCE=1`.
- Never print or persist the raw Codex thread ID outside Wire's existing hashed session-home mapping.
- Install the compatible binary before removing the fixed user-level `WIRE_SESSION_ID` override.
- Run GitNexus change detection before every commit and `test-env/run.sh` before merge.

---

### Task 1: Codex thread adapter regression

**Files:**
- Modify: `src/session.rs:838-867`
- Test: `src/session.rs:1847-2188`
- Modify: `test-env/runtime-210.sh:23-24`

**Interfaces:**
- Consumes: `resolve_session_key() -> Option<(String, &'static str)>` and `valid_session_key(&str) -> bool`.
- Produces: `CODEX_THREAD_ID` resolution with source `codex-cli`, after `CODEX_SESSION_ID` and before Copilot.

- [ ] **Step 1: Extend the existing Codex test with a failing thread-ID case**

Snapshot and clear `CODEX_THREAD_ID` beside `CODEX_SESSION_ID`, then add these assertions before setting `CODEX_SESSION_ID`:

```rust
unsafe { std::env::set_var("CODEX_THREAD_ID", "019f1111-1111-7111-8111-111111111111") };
let thread_a = resolve_session_key();
assert!(
matches!(&thread_a, Some((key, source))
if key == "019f1111-1111-7111-8111-111111111111" && *source == "codex-cli"),
"CODEX_THREAD_ID must resolve as codex-cli; got {thread_a:?}"
);
let thread_home_a = session_home_for_key(&thread_a.as_ref().unwrap().0).unwrap();
unsafe { std::env::set_var("CODEX_THREAD_ID", "019f2222-2222-7222-8222-222222222222") };
let thread_home_b = session_home_for_key(&resolve_session_key().unwrap().0).unwrap();
assert_ne!(thread_home_a, thread_home_b);
```

- [ ] **Step 2: Run the focused test to prove RED**

Run:

```bash
cargo test session::tests::resolve_session_key_codex_cli_adapter_and_priority -- --exact --nocapture
```

Expected: FAIL because `resolve_session_key` does not yet read `CODEX_THREAD_ID`.

- [ ] **Step 3: Add the minimal adapter and precedence assertions**

Insert the adapter immediately after `CODEX_SESSION_ID`:

```rust
("CODEX_SESSION_ID", "codex-cli"),
("CODEX_THREAD_ID", "codex-cli"),
```

Update the function documentation to state Codex currently exposes `CODEX_THREAD_ID`. In the test, assert `CODEX_SESSION_ID` wins when both Codex variables exist, `WIRE_SESSION_ID` wins over both, and `${SOME_PLACEHOLDER}` in `CODEX_THREAD_ID` never resolves.

- [ ] **Step 4: Make adapter tests and the runtime harness hermetic**

In the VS Code, Copilot, and Codex adapter tests, snapshot, clear, and restore `CODEX_THREAD_ID` beside `CODEX_SESSION_ID`. Add `CODEX_THREAD_ID` to the `unset` list in `test-env/runtime-210.sh`.

- [ ] **Step 5: Run focused verification**

Run:

```bash
cargo test resolve_session_key_ -- --nocapture
cargo fmt -- --check
cargo clippy --all-targets --all-features -- -D warnings
```

Expected: all adapter tests PASS; formatting and clippy exit 0.

- [ ] **Step 6: Commit the adapter**

Run:

```bash
git add src/session.rs test-env/runtime-210.sh
node .gitnexus/run.cjs detect-changes --scope staged --repo wire
git diff --cached --check
git commit -m "fix: resolve Codex identities by thread"
```

Expected: change detection lists only session-key resolution/test scope; commit succeeds.

### Task 2: Canonical verification and session record

**Files:**
- Modify: `SESSION_LOG_2026_07_18.md`

**Interfaces:**
- Consumes: Task 1's adapter and tests.
- Produces: canonical gate evidence and auditable deployment commands.

- [ ] **Step 1: Run the canonical CI mirror**

Run:

```bash
test-env/run.sh
```

Expected: exit 0 with every gate section passing.

- [ ] **Step 2: Record results without raw identifiers**

Append the RED failure, focused PASS commands, canonical gate result, and planned config migration to `SESSION_LOG_2026_07_18.md`. Record only presence and precedence of environment variables, never their values.

- [ ] **Step 3: Review the actual branch diff**

Run:

```bash
git diff --check origin/main...HEAD
node .gitnexus/run.cjs detect-changes --scope compare --base-ref main --repo wire
git diff --stat origin/main...HEAD
git diff origin/main...HEAD -- src/session.rs test-env/runtime-210.sh SESSION_LOG_2026_07_18.md
```

Expected: only design, plan, session adapter, runtime isolation, and session log changes.

- [ ] **Step 4: Commit the verification record**

Run:

```bash
git add SESSION_LOG_2026_07_18.md
node .gitnexus/run.cjs detect-changes --scope staged --repo wire
git diff --cached --check
git commit -m "docs: record Codex identity verification"
```

Expected: documentation-only commit succeeds.

### Task 3: Protected merge and managed deployment

**Files:**
- Modify after compatible install: `~/.codex/config.toml` (remove only the fixed `WIRE_SESSION_ID` entry)

**Interfaces:**
- Consumes: verified branch and existing launchd services.
- Produces: merged Wire binary plus future Codex processes keyed by their unique thread IDs.

- [ ] **Step 1: Push and open the pull request**

Run:

```bash
git push -u origin fix/codex-thread-identity
gh pr create --base main --head fix/codex-thread-identity --fill
```

Expected: branch pushed and PR URL returned.

- [ ] **Step 2: Wait for every protected check, then merge**

Run:

```bash
pr_number="$(gh pr view --json number --jq .number)"
gh pr checks --watch "$pr_number"
gh pr merge "$pr_number" --merge
```

Expected: all required checks pass and GitHub reports a merge commit on `main`.

- [ ] **Step 3: Build and atomically install the merged binary**

Build merged `main` in the isolated worktree, compare SHA-256 hashes for candidate and staged file, preserve the currently installed binary as `~/.cargo/bin/wire.pre-codex-thread-20260718`, then atomically move the staged candidate onto `~/.cargo/bin/wire`.

- [ ] **Step 4: Remove only the fixed override**

Back up `~/.codex/config.toml` with mode and timestamps preserved. Remove only
the `WIRE_SESSION_ID` assignment from `[shell_environment_policy.set]` without
printing its value.

Do not rewrite the file or edit shell dotfiles. Existing Codex processes keep their inherited identity; fresh processes use `CODEX_THREAD_ID`.

- [ ] **Step 5: Verify managed runtime state**

Run read-only checks for `wire service status`, launchd service state, daemon/MCP process counts and aggregate RSS, TCP health on `127.0.0.1:8771`, binary version/hash consistency, and `wire doctor`. Use isolated tests for thread-key behavior; do not mint probe identities in the live Wire root.

Expected: local relay healthy, daemon count stays bounded, no new version skew, doctor no longer reports the fixed-literal operator collision, and no destructive cleanup is needed.
55 changes: 55 additions & 0 deletions docs/superpowers/specs/2026-07-18-codex-thread-identity-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Codex thread identity adapter design

## Goal

Give each Codex thread a stable, independent Wire identity without a shared
literal `WIRE_SESSION_ID` override.

## Evidence

Codex exposes a unique `CODEX_THREAD_ID` to tool subprocesses. It does not
expose the `CODEX_SESSION_ID` name Wire currently reads. The fixed user-level
`WIRE_SESSION_ID` therefore wins for every concurrent Codex process and
collapses them onto one Wire home.

## Selected change

Add `CODEX_THREAD_ID` to `resolve_session_key` immediately after the existing
`CODEX_SESSION_ID` adapter. Both names report the `codex-cli` source label.
Keep `WIRE_SESSION_ID` as the highest-priority operator override and preserve
all other host adapters and fallback behavior.

This makes the live Codex caller work without launcher interpolation or
process/rollout-file discovery. After deploying the change, remove the fixed
user-level `WIRE_SESSION_ID`; existing sessions keep their current identity
until restarted, while fresh Codex threads resolve from their thread ID.

## Rejected approaches

- Map the thread ID in static Codex configuration. Static TOML cannot expand a
per-thread runtime value reliably for MCP children.
- Discover the thread through rollout filenames or process ancestry. That adds
filesystem timing and host-internal coupling when a stable environment value
already exists.

## Safety and compatibility

- Never print or persist the raw thread ID outside the existing hashed session
home mapping.
- Reject empty and unexpanded `${...}` values through `valid_session_key`.
- Existing explicit `WIRE_SESSION_ID`, Claude Code, Copilot, and VS Code
precedence remains unchanged.
- The live config override is removed only after the compatible binary is
installed.

## Verification

- A focused test must fail before implementation because two distinct
`CODEX_THREAD_ID` values currently resolve to no Codex adapter.
- Focused session tests must cover distinct IDs, `CODEX_SESSION_ID` precedence,
`WIRE_SESSION_ID` precedence, and placeholder rejection.
- Run `test-env/run.sh`, GitNexus change detection, protected GitHub checks, and
an isolated live probe with the fixed override absent from the probe only.
- After deployment and config correction, fresh Codex sessions must resolve to
distinct by-key homes and `wire doctor` must clear the literal-override
warning.
1 change: 1 addition & 0 deletions src/cli/pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ fn run_wire_for_session(home: &std::path::Path, args: &[&str], one_way: bool) ->
.env_remove("WIRE_SESSION_ID")
.env_remove("CLAUDE_CODE_SESSION_ID")
.env_remove("CODEX_SESSION_ID")
.env_remove("CODEX_THREAD_ID")
.env_remove("COPILOT_AGENT_SESSION_ID")
.env_remove("VSCODE_GIT_REPOSITORY_ROOT");
if one_way {
Expand Down
1 change: 1 addition & 0 deletions src/cli/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,7 @@ fn drive_bilateral_pair(
.env_remove("WIRE_SESSION_ID")
.env_remove("CLAUDE_CODE_SESSION_ID")
.env_remove("CODEX_SESSION_ID")
.env_remove("CODEX_THREAD_ID")
.env_remove("COPILOT_AGENT_SESSION_ID")
.env_remove("VSCODE_GIT_REPOSITORY_ROOT")
.env_remove("RUST_LOG")
Expand Down
Loading