<!-- task=m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness plan=4 tag=REVIEW_TEST milestone-task=claude-smoke -->

# Close the Claude supervisor lifecycle and deterministic cleanup evidence

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G09.md` is the mandatory final implementation step. Execute this packet exactly, run every verification command, paste actual stdout/stderr, keep both active files in place, and report ready for review. Only the official code-review skill may append a verdict, archive files, write `complete.log`, or classify the next state. If blocked, record only the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence fields; do not ask the user, call user-input tools, create control-plane stop files, change the owner/write boundary, archive logs, or write `complete.log`.

## Background

The timing and Messages-route repairs are correct, but process-group closure remains incomplete. The supervisor exits when the direct Claude process exits even if a descendant remains, its pre-assignment signal window can leave a newly created group unfenced, and the descendant-readiness fixture intermittently reads a nonexistent file. This follow-up closes that single process-lifecycle invariant and restores deterministic credential-free evidence without claiming actual Claude/Mac qualification.

## Archive Evidence Snapshot

- Reviewed plan: `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/plan_cloud_G08_3.log`; reviewed implementation/review: `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/code_review_cloud_G09_3.log`; earlier loop evidence remains in the same task directory.
- Verdict: FAIL. Required R3 remains open because normal direct-child exit does not settle the process group, the spawn/assignment signal window can exit without fencing the child, and the readiness fixture reads the descendant PID path before proving it exists.
- Fresh review evidence: dependency, shell syntax, model/verifier inventory, target inventory, aggregate isolation, and `git diff --check` passed. Direct self-test executions produced two `unexpected self-test failure` results and one pass; the Make self-test target failed. A focused process-group reproduction returned `direct_status=0 descendant_alive_after_direct_exit=true`.
- Roadmap carryover: `milestone-task=claude-smoke` maps to approved SDD S12. This packet repairs only the credential-free cleanup oracle and must not claim actual Claude/Mac qualification.

## Finding Resolution Map

| Finding | Mode | Exact fix | Changed precondition |
|---------|------|-----------|----------------------|
| R3 | `direct-fix` | Make `scripts/e2e-single-request-claude.sh` settle the Claude process group on normal, failure, and signal paths; close the pre-assignment signal window; make descendant readiness safe; and add deterministic leader-exit and early-signal regressions. | Repeated self-tests can pass only after every owned group is boundedly empty and every readiness probe is race-safe. |

`ownership_closed=true`: the supervisor and all credential-free fixtures are contained in the existing harness; no external runner, credential, user decision, or unordered dependency is required.

## Analysis

### Files Read

- `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/plan_cloud_G08_3.log`
- `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/code_review_cloud_G09_3.log`
- `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/code_review_cloud_G10_2.log`
- `scripts/e2e-single-request-claude.sh`
- `scripts/fixtures/single-request-claude-smoke-manifest.schema.json`
- `Makefile`
- `apps/edge/internal/service/single_request_observation.go`
- `apps/edge/internal/openai/routes.go`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log`

### SDD Criteria

- Approved and unlocked SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`.
- First-line scope remains `milestone-task=claude-smoke`, mapped to Acceptance Scenario S12.
- S12 and its Evidence Map require actual Claude, ingress count one, stage/total timing, terminal one, and workspace before/after evidence. This local packet protects the bounded process/raw-artifact precondition for that later external evidence; it does not satisfy S12 by itself.

### Verification Context

- No verification handoff was supplied. Repository-native evidence came from the harness, its embedded self-test, the Make target, production observation and route oracles, the approved SDD, testing rules, and the local testing-smoke profile.
- Current host: Linux `aarch64`; Go `1.26.2`, Python `3.12.3`, GNU Bash `5.2.21`. No remote runner, installed Claude, provider credential, or live IOP endpoint is configured or required for this repository-owned repair.
- Fresh reviewer output contradicted the recorded deterministic pass: two direct self-test attempts and the Make target failed with `unexpected self-test failure`, while one direct retry passed. Static inspection ties that exception class to `read_text()` on the not-yet-created descendant PID path.
- A focused process-group reproduction showed that the supervisor's normal `child.wait()` path permits a same-session descendant to remain alive after the leader exits.
- External Verification Preflight: not applicable. Actual Mac/Claude S12 qualification remains owned by the later external execution task.

### Test Coverage Gaps

- The existing TERM-resistant case covers a signal after both leader and descendant start, but its readiness check is itself racy because the PID file is not created by fixture setup.
- No existing case covers a direct leader that exits normally while a TERM-resistant descendant remains in the session.
- No deterministic case covers a signal arriving after process creation but before supervisor ownership is published to the signal handler.

### Symbol References

- None. No public symbol, Make target, schema key, contract field, or production API is renamed or removed.

### Split Judgment

- Keep one plan. Normal exit, failure exit, early signal, process-group settlement, and fixture readiness are one lifecycle invariant in one script and must pass together.
- Runtime predecessor `22+21_executor_activation` is satisfied by `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log`.

### Scope Rationale

- Include only `scripts/e2e-single-request-claude.sh` and the active review evidence file.
- Exclude the schema, Makefile, production Edge/Node runtime, specs, contracts, roadmap state, credentials, deployment, and actual Claude/Mac execution because their contracts are unchanged and the defect is local to supervisor ownership and embedded fixtures.

### Final Routing

- `status=routed`, `evaluation_mode=isolated-reassessment`, `missing_evidence=[]`, `blocked_reason=none`; all build/review closure fields are true and there is no capability gap.
- Build scores `1/2/1/2/2` => G08, base `local-fit`, final `recovery-boundary`, `worker/cloud/G08`, `PLAN-cloud-G08.md`.
- Review scores `2/2/1/2/2` => G09, `official-review`, `review/cloud/G09`, `CODE_REVIEW-cloud-G09.md`.
- `large_indivisible_context=false`; positive loop risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, and `variant_product` (4). `review_rework_count=3`; `evidence_integrity_failure=true`; risk and recovery boundaries both match, with recovery precedence. Finalizer: `finalize-task-policy.sh`, mode `pair`.

## Dependencies and Execution Order

1. The encoded predecessor `22+21_executor_activation` is complete at `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log`.
2. Preserve the corrected terminal-duration and 401/405 route-preflight behavior as read-only regressions.
3. Close supervisor ownership on every exit path, then add the race-safe lifecycle fixtures and run the repeated suite.

## Implementation Checklist

- [ ] Settle the Claude process group with one bounded normal/failure/signal lifecycle, preserve the direct exit status only after group closure, and close the pre-assignment signal window.
- [ ] Make descendant readiness race-safe and add deterministic leader-exit-with-descendant and early-signal regressions that prove bounded exit, no surviving process, no raw capture, and no partial/final publication.
- [ ] Run dependency, syntax, three fresh self-tests, Make self-test, process-coverage inventory, model/verifier inventory, target inventory, aggregate isolation, and diff verification without claiming S12 qualification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_TEST-1] Close every supervisor process-group exit path

**Problem**

`scripts/e2e-single-request-claude.sh:786` returns the direct child's status without checking or terminating remaining members of its process group. Lines 753-771 also raise from the signal handler when `child` is still unset, so a signal during `Popen` can abandon a just-created group. The supervisor therefore does not satisfy R3's complete ownership invariant.

**Solution**

Use one idempotent bounded settlement routine for normal, non-zero, and signal exits. Preserve the direct status, terminate any remaining process-group members with TERM then KILL after the fixed grace, wait for the group to disappear, and only then return. Record an early signal while ownership is not yet assigned and service it immediately after assignment instead of exiting unfenced.

Before (`scripts/e2e-single-request-claude.sh:753` and `:786`):

```python
def terminate(signum, _frame):
    if child is not None and child.poll() is None:
        ...
    raise SystemExit(128 + signum)

raise SystemExit(child.wait())
```

After:

```python
def settle_group():
    # Bounded TERM -> KILL -> group-empty check for every exit path.
    ...

status = child.wait()
settle_group()
raise SystemExit(status)
```

The exact implementation must also defer an early signal until `child` ownership is assigned and then run the same settlement routine.

**Modified Files and Checklist**

- [ ] Update the embedded Python supervisor in `scripts/e2e-single-request-claude.sh`.
- [ ] Keep raw capture/output cleanup after process-group settlement and preserve existing secret/redaction behavior.

**Test Strategy**

Extend the embedded self-test in `scripts/e2e-single-request-claude.sh`. Retain the TERM-resistant leader+descendant case, add a leader that exits while its TERM-resistant descendant remains, and add a deterministic early-signal seam. Every case must prove the complete group disappears within the harness bound and leaves no raw or publication artifact.

**Verification**

Run `bash -n scripts/e2e-single-request-claude.sh && ./scripts/e2e-single-request-claude.sh --self-test`; it must exit zero without a surviving process or `unexpected self-test failure`.

### [REVIEW_TEST-2] Make cleanup regressions deterministic

**Problem**

`scripts/e2e-single-request-claude.sh:1191` stores a path for the descendant PID but does not create it. Line 1520 calls `read_text()` before checking existence, producing the observed intermittent generic exception and contradicting the recorded self-test evidence.

**Solution**

Initialize the readiness file or guard existence before every read, require a non-empty parseable PID before signaling, and keep the readiness deadline independent for each stage. Exercise the full self-test repeatedly so a stale scheduling-dependent success cannot close the task.

Before (`scripts/e2e-single-request-claude.sh:1520`):

```python
while time.time() < deadline and not signal_fixture["descendant"].read_text().strip():
```

After:

```python
while time.time() < deadline:
    if descendant_path.exists() and descendant_path.read_text().strip():
        break
    time.sleep(0.05)
```

**Modified Files and Checklist**

- [ ] Repair fixture initialization/readiness in `scripts/e2e-single-request-claude.sh`.
- [ ] Add exact assertions for normal-leader exit, early signal, bounded cleanup, and repeated suite stability in the same file.

**Test Strategy**

Use the existing credential-free embedded suite; no separate test file is needed because it already owns the fake Claude, HTTP listener, process lifecycle, raw root, output directory, and publication assertions. Run it three fresh times plus once through Make.

**Verification**

Run `bash -c 'set -euo pipefail; for attempt in 1 2 3; do ./scripts/e2e-single-request-claude.sh --self-test; done'`; all three attempts must exit zero.

## Modified Files Summary

| File | Items |
|------|-------|
| `scripts/e2e-single-request-claude.sh` | REVIEW_TEST-1, REVIEW_TEST-2 |
| `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/CODE_REVIEW-cloud-G09.md` | REVIEW_TEST-1, REVIEW_TEST-2 implementation evidence |

## Final Verification

Fresh output is required; cached or reconstructed output is not acceptable.

1. `bash -c 'set -euo pipefail; shopt -s nullglob; candidates=(agent-task/m-iop-owned-single-request-agent-execution/22_*/complete.log agent-task/m-iop-owned-single-request-agent-execution/22+*/complete.log agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/22_*/complete.log agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/22+*/complete.log); printf "%s\n" "${candidates[@]}"; ((${#candidates[@]} == 1))'` — prints exactly the archived task-22 completion path and exits zero.
2. `bash -n scripts/e2e-single-request-claude.sh` — exits zero with no output.
3. `bash -c 'set -euo pipefail; for attempt in 1 2 3; do ./scripts/e2e-single-request-claude.sh --self-test; done'` — all three fresh credential-free runs pass without generic exceptions or surviving processes.
4. `make test-single-request-claude-smoke-self-test` — runs the same fresh suite and exits zero.
5. `bash -c "set -euo pipefail; rg --sort path -n 'leader-exit|early-signal|term-resistant|descendant' scripts/e2e-single-request-claude.sh; ! rg --fixed-strings 'raise SystemExit(child.wait())' scripts/e2e-single-request-claude.sh"` — lists the three process-lifecycle fixture families and proves the direct wait is not an immediate supervisor exit.
6. `bash -c "set -euo pipefail; rg --fixed-strings 'ANTHROPIC_MODEL=\"\$MODEL\"' scripts/e2e-single-request-claude.sh; ! rg --fixed-strings 'IOP_SINGLE_REQUEST_SMOKE_MODEL=\"\$MODEL\"' scripts/e2e-single-request-claude.sh; ! rg --fixed-strings \"'exit_code':0\" scripts/e2e-single-request-claude.sh"` — preserves actual model input and derived verifier evidence.
7. `rg --sort path -n 'test-single-request-claude-smoke|IOP_SINGLE_REQUEST_SMOKE_' Makefile` — lists the four isolated targets and only caller-supplied inputs.
8. `bash -c 'set -euo pipefail; if rg --sort path -n "test-single-request-claude-smoke([^:]*):.*test-single-request-claude-smoke$" Makefile; then exit 1; else test $? -eq 1; fi'` — exits zero only when no aggregate target includes the credentialed run.
9. `git diff --check` — exits zero with no whitespace errors.

Repository Edge-Node diagnostic, auxiliary E2E smoke, and full-cycle actual Claude/Mac execution are not run in this packet. External S12 qualification remains outside this repository-owned cleanup repair.

After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
