# Preset Tmpfile Cleanup Implementation Plan

> **For agentic workers:** this plan is dispatched by `bin/runplan` (mega-plan-harness native engine), not ship/sdd.

**Goal:** clean up the synthetic runconfig temp file that `--preset` writes to `os.tmpdir()`, and cover it with a test.

**Architecture:** wrap the existing per-run logic in `main()` in a try/finally that deletes the synthetic file (only the one this invocation created) after the run completes, success or failure.

**Tech Stack:** Node.js (`src/runner.js`), bash (`test/runner-integration.sh`).

---

## Wave Plan

| Wave | Tasks | Files touched | Safe to parallelize? |
|------|-------|----------------|----------------------|
| 1 | Task 1 | src/runner.js | single task |
| 2 | Task 2 | test/runner-integration.sh | single task |

## Task 1: clean up synthetic runconfig temp file

**Wave:** 1
**Blocks:** Task 2
**Blocked by:** —

**Files:**
- Modify: `src/runner.js:15-76` (`main()`) — track the synthetic file path when created, delete it in a `finally` after the run, success or failure.

**Contract:**
- Only delete a file this invocation itself created via the `args.preset && !args.runconfigPath` branch (line 31-38). NEVER delete a user-supplied `--runconfig` file.
- Deletion happens exactly once per invocation, after `await runner.run()` (or the resume path) completes or throws — use `finally`, not a duplicate call on both the success and catch paths.
- Missing file at cleanup time (already gone) must not throw — use a non-throwing removal (e.g. `fs.rm(path, { force: true })` or catch+ignore ENOENT).

**Behavior:**
- `--preset` run without `--runconfig`: after process exits (success or error), the synthetic `harness-runconfig-<pid>-<hex>.json` file under `os.tmpdir()` no longer exists.
- `--runconfig <file>` run (no `--preset`): that file is untouched after the run — never deleted (it wasn't synthesized).
- Resumed run (`--resume` + `--preset`): each invocation synthesizes and cleans up its own file independently — no cross-invocation leakage.

**Acceptance (one executable check):**
- Run: `node -e "require('./src/runner.js')" 2>&1 | head -1` (module still loads — sanity import check)
- Expected: no output (no syntax/require errors)
- Full behavioral acceptance is Task 2's test.

- [ ] Implement the try/finally cleanup per the contract above
- [ ] Run the sanity import check → expected output above
- [ ] Commit: `git add src/runner.js && git commit -m "fix: clean up synthetic --preset runconfig temp file after run"`

## Task 2: test synthetic runconfig cleanup

**Wave:** 2
**Blocks:** —
**Blocked by:** Task 1

**Files:**
- Modify: `test/runner-integration.sh` — add a case using the existing `mkfixture()`/`ok()`/`bad()` pattern in that file.

**Contract:**
- New test case function, called from the file's existing case-runner section (read the file to match the exact registration pattern already used for other cases).

**Behavior:**
- Case A: run the fixture with `--preset stub` (no `--runconfig`), capture the synthetic file's expected glob path in `os.tmpdir()` equivalent (`${TMPDIR:-/tmp}/harness-runconfig-*`) before the run, assert zero matching files remain after the process exits.
- Case B: run the fixture with `--runconfig <fixture-runconfig.json>` (no `--preset`), assert that exact file STILL exists after the run (never deleted).

**Acceptance (one executable check):**
- Run: `bash test/runner-integration.sh`
- Expected: all existing cases PASS plus the 2 new ones, exit 0

- [ ] Write the two cases per the contract above
- [ ] Run the acceptance check → expected output above
- [ ] Commit: `git add test/runner-integration.sh && git commit -m "test: cover synthetic --preset runconfig temp file cleanup"`
