---
quick_task: 260419-6i2
one_liner: "Handoff-safe DEPLOY.ps1 — remove kill-owl step; targeted cache prune (keep NEW + PREVIOUS + installPath leaf); align DEPLOY.md prose"
tags: [deploy, powershell, handoff, cache, docs]
commit: 4a3a87e
files_modified:
  - docs/DEPLOY.ps1
  - docs/DEPLOY.md
tasks_completed: 2
completed_at: 2026-04-19
---

# Quick Task 260419-6i2 Summary

## Objective

Make `docs/DEPLOY.ps1` safe to run while live `owl.exe` processes are active so the Phase 18.4/18.5 seamless-handoff pipeline actually fires on the next deploy. Previously the script killed `owl.exe` and wiped the entire cache tree — both correct before handoff shipped, both wrong now.

## What Changed

### `docs/DEPLOY.ps1`

- **Removed Step 2 (kill `owl.exe`).** The `Get-Process -Name 'owl'` + `Stop-Process` block is gone. Replaced with a brief stub comment explaining the Phase 18.4/18.5 handoff rationale and noting that `Write-Step` labels keep their historical numbers (Step 3/4/5/6-7/8/9) so log-grep tooling still matches.
- **Replaced cache wipe with targeted prune.**
  - Captures `$prevVersion` and `$prevInstallPath` from `~/.claude/plugins/installed_plugins.json` BEFORE step 9 (`claude plugin install spt@cplugs`) rewrites it. Safe under `-DryRun` (read-only JSON parse). Parse errors are caught and treated as first-deploy.
  - Syncs the NEW version into the cache FIRST (unchanged copy block), then enumerates plain-semver children (`^\d+\.\d+\.\d+(-\w+)?$`) and removes any NOT in the keep-set `{$Version, $prevVersion, leaf($prevInstallPath)}`.
  - Sharing-violation residue (the .exe is mapped by a running process) is logged as a warning via `Write-Warn` and retried next deploy — never fatal. Uses `Remove-Item -ErrorAction SilentlyContinue` plus a post-check `Test-Path` to detect the residue case.
  - `-DryRun` mode emits `[DRY-RUN] would remove <full path>` for each candidate; mutates nothing.
  - First-deploy guard: if BOTH `$prevVersion` and `$prevInstallPath` are null (no prior `spt@cplugs` entry), the entire prune block is skipped with an explicit `Write-SubStep "First deploy detected ... skipping prune."` log.
- **Header `.DESCRIPTION` block** renumbered (eight user-facing steps now, not nine); cache-step prose rewritten to describe targeted prune; explicit NOTE paragraph added explaining why the kill step is absent.

### `docs/DEPLOY.md`

- "What it does (in order)" bullet list renumbered to 1-8; cache bullet rewritten to describe targeted prune, keep-set, and sharing-violation tolerance.
- "What it does NOT do" section gained two new bullets:
  - *"Kill running `owl.exe` processes"* — explains Phase 18.4/18.5 handoff + why killing aborts it.
  - *"Wipe the entire cache tree"* — explains why the old `rm -rf cache/cplugs/spt` yanks the `.exe` out from under running processes mid-handoff.
  The old "Stop live agents (it kills `owl.exe` processes)" bullet was deleted.
- "Update Marketplace Plugin Files" step 5 prose + shell snippet rewritten for manual users: no more `rm -rf ~/.claude/plugins/cache/cplugs/spt` / "stop live agents first"; new flow syncs `$CACHE` dir, leaves PREVIOUS version in place, points users at DEPLOY.ps1 for automatic keep-set logic.
- Cross-references updated (`-PluginDir` parameter now references "step 6" not "step 7"; troubleshooting section ditto; intro paragraph no longer quotes specific step numbers).

## Verification

### Automated (run from bash on Windows)

**PowerShell syntax parse** — `[System.Management.Automation.Language.Parser]::ParseFile` on `docs/DEPLOY.ps1` → `PARSE_OK` (no syntax errors).

**Dry-run against live populated install** (`powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -DryRun`):

| Expected phrase | Present? |
|---|---|
| `Step 2: Kill` | Absent (correct) |
| `Remove $CacheRoot (recursive)` | Absent (correct) |
| `Step 3: cargo build --release` | Present |
| `Step 6-7: Sync new version + prune old cache` | Present |
| `Prior version: 1.8.8 (installPath leaf: 1.8.8)` | Present (captured from `installed_plugins.json`) |
| `Keep-set: 1.8.8` | Present |
| `No prune candidates (only keep-set versions present).` | Present (only `1.8.8` in cache, in keep-set → nothing to prune, correct) |
| `Step 8: Clean up rogue manual-install dir` | Present |
| `Step 9: Refresh plugin state via Claude Code CLI` | Present |

Script exits 0, no throws.

**grep sweep** on `docs/DEPLOY.md` for `(kills any running|kills \`owl.exe\`|Windows binary-lock|Wipes \`~|wipe entire|WIPE ALL)` → no matches. Remaining hits of `(kill|wipe)` in the file are either:
- new "What it does NOT do" prose explaining why those are wrong (intentional),
- unrelated `~/.claude/skills/owl` directory cleanup (pre-existing, orthogonal).

### Deferred to user UAT

The live-deploy UAT described in the plan's `<verification>` block (running `powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch` against an active doyle session and observing `HANDOFF_DEFER` → `HANDOFF wrapper: new target=...` → `handoff_hydrated=true`) is the USER's responsibility per quick-task constraints. The automated dry-run exercises every new code path (prior-version capture, keep-set build, candidate enumeration, branch selection, first-deploy guard) but only a real deploy against a running listener proves the full Phase 18.4/18.5 handoff pipeline stays intact end-to-end.

## Deviations from Plan

None — plan executed as written. Two minor adjustments kept within the plan's explicit guidance:

- The plan suggested "Step 2 removed" could be either silent deletion or a one-line stub comment; I went with the stub (for maintainers who expect Step 2 to exist per the plan's "optionally leave a one-line stub" language).
- The plan asked me to "remove the trailing sentence in `.PARAMETER PluginDir`'s prose and any other place that says 'Windows binary-lock mitigation'". The only occurrence of that phrase was in the `.DESCRIPTION` step-2 bullet; the `.PARAMETER PluginDir` block never contained it. Updated the `.DESCRIPTION` as specified; `.PARAMETER PluginDir` needed no edit. Ran `grep -i 'binary.{1,5}lock'` against the final DEPLOY.ps1 and DEPLOY.md — zero matches.

## Self-Check

- `docs/DEPLOY.ps1` exists and parses (PARSE_OK).
- `docs/DEPLOY.md` exists.
- Commit `4a3a87e` exists in `git log`.
- `powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -DryRun` exits 0 with the expected log markers above.

## Self-Check: PASSED

## Follow-Ups (captured from plan)

Out of scope for this quick task:

- `scripts/fake_handoff_deploy.sh` hardcodes `CURRENT_VER_DIR=1.8.7` and `FAKE_VER=1.8.8-test`. Each real plugin-version bump drifts the harness. Preferred fix: teach the harness to read `installed_plugins.json` dynamically (same pattern DEPLOY.ps1 now uses for `$prevInstallPath`). Capture as a follow-up todo.
- A Pester test that mocks `installed_plugins.json` + a fake cache tree, runs DEPLOY.ps1 `-DryRun`, and asserts keep-set behavior. Would harden against future regression. Worth adding if/when the deploy script grows further.
