diff --git a/docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md b/docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md index 5702076..3fcf586 100644 --- a/docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md +++ b/docs/superpowers/plans/2026-05-08-v0.5.3-smoke-test-checklist.md @@ -1,76 +1,137 @@ -# v0.5.3 Manual Smoke-Test Checklist (WSL + Native Windows) +# v0.5.3 Manual Smoke-Test Checklist (WSL + Native Windows) — v2 **Branch:** `feat/v0.5.3-persistent-session-mode` -**Build artifact for WSL:** `dist/ctask-linux-amd64` (already produced by `just build-linux`) +**Build artifact for WSL:** `dist/ctask-linux-amd64` -This checklist covers what the automated test suite cannot exercise: TTY + -tmux interaction, real workspace creation/launch, multi-terminal reattach. -Run each step in order; record PASS/FAIL inline as you go. +This v2 checklist fixes two issues from v1: + +1. **Three explicit terminals.** Each command labels which terminal it goes + in. Persistent mode locks the foreground terminal in a polling loop, so + subsequent commands MUST use a different terminal. +2. **Realistic "what you'll see" notes.** The `[ctask] created ...` banner + is painted-over by tmux's alternate-screen mode within ~50ms. Inside + tmux you only see a bash prompt and a status bar — that is correct. + +--- + +## Terminals you'll need + +Open all three at the start. They are referenced by label throughout. + +| Label | What | Used for | +|-------|------|----------| +| **WSL-A** | A WSL `debian-dev` terminal | The "foreground ctask" terminal: every `ctask new`/`resume`/`attach` invocation runs here. After tmux attach, this terminal is locked in the persistent-mode polling loop until the tmux session ends or you Ctrl-C. | +| **WSL-B** | A SECOND WSL `debian-dev` terminal | For `tmux ls`, `pgrep`, `kill`, and SECONDARY `ctask resume` invocations that exercise passive reattach concurrent with the WSL-A owner. Always run with the same `CTASK_ROOT` and `PATH` exports as WSL-A (see Setup S2). | +| **PS-C** | A Windows PowerShell 7 terminal | Only for the native-Windows refusal test in section 10. | + +> **Important:** If at any point in WSL-A you see your bash prompt but the +> shell appears unresponsive (won't run `ctask ls` etc), you're inside +> tmux's outer screen with the foreground `ctask` process still polling. +> Open WSL-B for next commands, or Ctrl-C in WSL-A to kill `ctask` (which +> is what you want for the adopted-reattach test). --- ## Setup -### S1. Stage the binary on PATH inside WSL +### S1. (WSL-A) Stage the binary -The `claude` agent is not installed in your `debian-dev` WSL distro, so for -the smoke tests we substitute `bash` as the agent via `--agent bash`. The -purpose of these tests is the ctask + tmux dispatch — the agent is just a -"thing that runs inside tmux." +`claude` is not installed in your `debian-dev` distro, so the smoke tests +substitute `bash` as the agent via `--agent bash`. The point of these +tests is the ctask + tmux dispatch — the agent is just "a thing that runs +inside tmux." + +Run in **WSL-A**: ```bash -# Inside WSL (debian-dev): mkdir -p ~/.local/bin cp /mnt/c/Users/Warren/claude_tasks/ctask/dist/ctask-linux-amd64 ~/.local/bin/ctask chmod +x ~/.local/bin/ctask export PATH="$HOME/.local/bin:$PATH" ctask --version # Expect: ctask v0.5.3 -which tmux && tmux -V # Expect: /usr/bin/tmux, tmux 3.5a (or similar) +which tmux && tmux -V # Expect: /usr/bin/tmux + tmux 3.5a (or similar) ``` -If `~/.local/bin` is not already on PATH in your shell init, add `export PATH="$HOME/.local/bin:$PATH"` to `~/.bashrc`. +### S2. (WSL-A and WSL-B both) Set the smoke-test root -### S2. Choose a smoke-test root +Run in **BOTH WSL-A AND WSL-B** (a fresh shell doesn't inherit env vars): ```bash +export PATH="$HOME/.local/bin:$PATH" export CTASK_ROOT=/tmp/ctask-053-smoke mkdir -p "$CTASK_ROOT" ``` -Using a temp root keeps your real workspaces untouched. Cleanup is one -`rm -rf` at the end. +This keeps your real workspaces untouched — cleanup is one `rm -rf` later. --- ## Owner-create path (Step 3) -### O1. Enable persistent mode and create a project +### O1. (WSL-A) Enable persistent mode and create the project + +Run in **WSL-A**: ```bash export CTASK_SESSION_MODE=persistent ctask new --project --agent bash ctask-053-smoke ``` -**Expected:** +**What you'll see (and the expected reality, not the misleading v1 text):** -- A line like `[ctask] created projects/2026-05-08_ctask-053-smoke` -- Banner lines: `[ctask] local :: ctask-053-smoke` and the workspace path -- A bash prompt appears (you are inside tmux + bash + the workspace's - `ctask-053-smoke/` project subdirectory) -- Status line indicates you're attached to the tmux session +- For ~50ms, the outer terminal will print three lines (you may not have + time to read them — tmux clears the screen on attach): + - `[ctask] created projects/2026-05-08_ctask-053-smoke` + - `[ctask] local :: ctask-053-smoke` + - `[ctask] /tmp/ctask-053-smoke/projects/2026-05-08_ctask-053-smoke` + - `[ctask] project dir: ctask-053-smoke/` +- Then tmux's alternate screen takes over. You see: + - A bash prompt like `warren@DESKTOP-VGJVN77:/tmp/ctask-053-smoke/projects/2026-05-08_ctask-053-smoke/ctask-053-smoke$` + (the path includes the project subdir — that's the v0.5 `launch_dir`). + - A green status bar at the bottom showing something like + `[ctask-projects-ctask-053-smoke-XXXXXX:bash*]` on the left, with + your hostname and time on the right. **tmux truncates long session + names**: you may see only `[ctask-pro0:bash*]` when the bar is narrow + — that's normal, not a bug. -### O2. Detach without exiting +**This is the expected screen.** No "[ctask] adopting orphaned..." line +appears (that's adoption, not owner-create). The banner you see flashing +will be in the outer-terminal scrollback after you fully exit tmux — +checking it now is not necessary. -Press `Ctrl-b d` (default tmux prefix). You should return to your outer -shell and see no error. +PASS / FAIL: ___ -### O3. Verify the tmux session is alive +### O2. (Inside tmux in WSL-A) Detach without exiting + +Press `Ctrl-b d` (the default tmux prefix is Ctrl-b, then `d` for detach). + +**What you'll see:** + +- The tmux full-screen alternate display closes. Your outer WSL-A terminal + is restored. You'll see something like: + - The `[ctask] created projects/...` line and the banner lines printed + earlier (they're in scrollback). + - A line like `[detached (from session ctask-projects-ctask-053-smoke-XXXXXX)]`. +- **The shell prompt in WSL-A does NOT return.** The cursor sits on a new + line and your typed input is ignored. This is correct: the foreground + `ctask new` process is now in its persistent-mode polling loop, waiting + for the tmux session to actually end. +- This is why you need WSL-B for the next steps. Do NOT Ctrl-C in WSL-A + yet — that would kill the heartbeat and create an orphan, which we + exercise later. + +PASS / FAIL: ___ + +### O3. (WSL-B) Verify the tmux session is alive + +Switch to **WSL-B**, then run: ```bash tmux ls ``` -**Expected:** A line containing `ctask-projects-ctask-053-smoke-<6hex>`. +**Expected:** A line containing `ctask-projects-ctask-053-smoke-XXXXXX` +followed by `(1 windows) ... (attached)` or `(detached)`. PASS / FAIL: ___ @@ -78,22 +139,35 @@ PASS / FAIL: ___ ## Passive reattach (Step 4) -### P1. Reattach via resume +### P1. (WSL-B) Reattach via resume + +WSL-A is still locked in the polling loop. The reattach happens from a +DIFFERENT process. Run in **WSL-B**: ```bash ctask resume ctask-053-smoke ``` -**Expected:** +**What you'll see (passive reattach behavior):** -- Immediate reattach (sub-second). No "[ctask] adopting orphaned..." line. -- Same bash prompt as before. Scrollback intact (you can scroll up and see - the original banner). -- No new banner line is printed (passive reattach does not print one). +- Sub-second attach. +- The same bash prompt as before. Scrollback inside tmux is intact (you + can scroll up via `Ctrl-b [` then PageUp). +- **No `[ctask] adopting orphaned ...` line.** That's the discriminator + between passive reattach and adoption. +- **No new banner.** Passive reattach does not print one — only owner-create + and adoption do. -### P2. Detach again +PASS / FAIL: ___ -`Ctrl-b d`. +### P2. (Inside tmux in WSL-B) Detach again + +Press `Ctrl-b d` in WSL-B. + +WSL-B's shell prompt also won't return (same polling-loop behavior). That's +fine for now — both WSL-A and WSL-B are sitting in polling loops on the +same tmux session. Both will exit cleanly when the tmux session ends in +section A3 below. PASS / FAIL: ___ @@ -101,105 +175,140 @@ PASS / FAIL: ___ ## Adopted reattach (Step 5) -### A1. Simulate orphan: kill the heartbeating ctask process +The previous owner (the WSL-A `ctask new` process) needs to "die" while +the tmux session itself stays alive. Two clean ways to do this: -In a fresh outer shell (NOT inside the tmux session): +- **Easiest:** Press Ctrl-C in WSL-A. This SIGINT-kills the foreground + ctask process; its heartbeat stops; the tmux session keeps running. +- **Equivalent via pgrep+kill from a third place:** what v1 said. We use + Ctrl-C here to keep things simple. + +### A1. (WSL-A) Kill the owner ctask + +Switch to **WSL-A** (still showing `[detached (from session ...)]` and a +non-responsive cursor) and press `Ctrl-C`. + +**What you'll see:** + +- WSL-A's shell prompt finally returns. + +In **WSL-B** (which is now also in polling-loop mode after the P2 detach), +also press `Ctrl-C`. Its prompt should return too. + +### A2. (WSL-A) Verify the tmux session survived ```bash -pgrep -af 'ctask resume ctask-053-smoke' | head -5 -# Note the PID of the ctask process (the parent that owns the lease, NOT bash inside tmux). -# It is usually the most recent one. -pgrep -f 'ctask resume ctask-053-smoke' | xargs -r kill -9 +tmux ls ``` -The tmux session itself stays alive (only the ctask process owning the -lease is killed). Verify: +**Expected:** still shows `ctask-projects-ctask-053-smoke-XXXXXX (... detached)`. + +### A3. (WSL-A) Wait for the lease to go stale + +The freshness threshold is 60 seconds (`internal/session/heartbeat.go:15`). ```bash -tmux ls # Expected: ctask-projects-ctask-053-smoke- still listed +date; sleep 65; date ``` -Wait at least 60 seconds so the lease's last_heartbeat_at goes stale (the -threshold is 60s — see `internal/session/heartbeat.go:15`). - -### A2. Reattach — adoption should fire +### A4. (WSL-A) Reattach — adoption should fire ```bash ctask resume ctask-053-smoke ``` -**Expected:** +**What you'll see (the discriminating line):** -- A line on stderr: `[ctask] adopting orphaned persistent session (previous owner exited without finalizing)` -- Then the user is reattached to the same bash session inside tmux. +``` +[ctask] adopting orphaned persistent session (previous owner exited without finalizing) +``` -### A3. End the session and inspect the summary +— printed to stderr BEFORE tmux's alternate screen takes over. Then +you're inside tmux with the same bash session. -Inside the bash session: `exit` to leave bash. -Then: `Ctrl-b &` to confirm killing the tmux window, OR -`tmux kill-session -t ctask-projects-ctask-053-smoke-` from outside. +PASS / FAIL: ___ -Wait ~3 seconds (polling interval) for the foreground `ctask resume` to -detect session end and finalize. +### A5. (Inside tmux in WSL-A) End the session + +Type at the bash prompt: + +```bash +exit +``` + +That exits bash. With no remaining processes, tmux's window has nothing +to display, and the session ends. tmux's alternate screen closes; WSL-A's +outer terminal is restored. + +Wait ~3 seconds (the polling cadence) — the foreground `ctask resume` +detects session end and runs adoption finalize. WSL-A's prompt returns. + +### A6. (WSL-A) Inspect the summary ```bash WS=$CTASK_ROOT/projects/2026-05-08_ctask-053-smoke -cat "$WS/.ctask/last-session-summary.json" | python3 -m json.tool 2>/dev/null \ - || cat "$WS/.ctask/last-session-summary.json" +cat "$WS/.ctask/last-session-summary.json" | python3 -m json.tool ``` **Expected fields present:** ```json -{ - "end_reason": "tmux_session_ended", - "detected_via": "polling", - "session_ownership": "adopted", - "adopted_from_orphan_at": "2026-05-08T..." -} +"end_reason": "tmux_session_ended", +"detected_via": "polling", +"session_ownership": "adopted", +"adopted_from_orphan_at": "2026-05-08T..." ``` +(Other fields like session_id, hostname, files_added etc. will also be +present — those are unchanged from v0.4.) + PASS / FAIL: ___ --- ## Non-TTY refusal (Step 6) -### T1. Pipe stdin to break the TTY check +### T1. (WSL-A) Pipe stdin to break the TTY check + +Run in **WSL-A** (now back at a normal prompt): ```bash echo "" | ctask resume ctask-053-smoke ``` -**Expected:** A multi-line refusal message containing: -- `interactive terminal` +**Expected:** A multi-line refusal message containing all of: +- `ctask persistent mode requires an interactive terminal` - `ssh -t ctask resume ` - `ctask resume --direct` -Exit code: 1. +Exit code: 1 (`echo $?` to verify). -### T2. SSH-without-t equivalent (optional) +PASS / FAIL: ___ -If you have ssh server running on localhost: +### T2. (Optional, WSL-A) SSH-without-t equivalent + +Skip this if `sshd` is not running on localhost. ```bash ssh localhost -- bash -lc "PATH=\$HOME/.local/bin:\$PATH CTASK_SESSION_MODE=persistent CTASK_ROOT=$CTASK_ROOT ctask resume ctask-053-smoke" ``` -**Expected:** Same refusal message (no `-t` means no TTY allocation). +**Expected:** Same refusal message. (No `-t` means no TTY allocation.) -PASS / FAIL: ___ +PASS / FAIL (or N/A): ___ --- ## Nested tmux refusal (Step 7) +Run in **WSL-A**: + ```bash -TMUX=fake-tmux-env-value ctask resume ctask-053-smoke +TMUX=fake-value-not-a-real-tmux ctask resume ctask-053-smoke ``` -**Expected:** Refusal with text containing: -- `cannot attach while already inside tmux` +**Expected:** Refusal containing: +- `ctask persistent mode cannot attach while already inside tmux` - `ctask resume --direct` PASS / FAIL: ___ @@ -208,27 +317,37 @@ PASS / FAIL: ___ ## --direct confirmation (Step 8) -### D1. Recreate a tmux session for the workspace +### D1. (WSL-A) Recreate a tmux session for the workspace -If you killed it during the adoption test, recreate via owner-create: +You killed the previous session in A5 by `exit`-ing bash. Recreate it +with the easiest path: `ctask resume`. Run in **WSL-A**: ```bash ctask resume --agent bash ctask-053-smoke -# Detach with Ctrl-b d. -tmux ls # Expected: session listed again ``` -### D2. Try opening with --direct +(Inside tmux now.) Detach with `Ctrl-b d`. WSL-A is locked in polling +loop again — that's fine. + +In **WSL-B** verify: + +```bash +tmux ls # Expected: ctask-projects-ctask-053-smoke-... listed +``` + +### D2. (WSL-B) Try opening with --direct + +While the tmux session exists, run in **WSL-B**: ```bash ctask open --direct ctask-053-smoke ``` -**Expected (interactive prompt):** +**Expected (interactive Y/N prompt):** ``` A persistent tmux session exists for this workspace: - ctask-projects-ctask-053-smoke- + ctask-projects-ctask-053-smoke-XXXXXX Opening a direct-mode shell may create conflicting workspace activity. The recommended path is: @@ -237,22 +356,42 @@ The recommended path is: Continue with --direct anyway? [y/N] ``` -Type `n` and press Enter. Expected: exits with `Error: canceled by user`. +Type `n` then Enter. Expected: exits with `Error: canceled by user`. -### D3. Confirm --direct then bypass - -Run again: +### D3. (WSL-B) Confirm --direct then bypass ```bash ctask open --direct ctask-053-smoke ``` -Type `y` and press Enter. Expected: the v0.4 active-session warning fires -(an EXISTING tmux session means there's an active lease too). Either -answer to that prompt is fine — the goal is just to confirm the --direct -prompt fired and the user can proceed past it. +Type `y` then Enter. Expected: the --direct prompt is bypassed, then the +v0.4 active-session warning fires (an existing tmux session means there's +also an active lease). At the second prompt you can answer either way — +the goal is to confirm the --direct prompt fired and was acceptable. -Detach / exit any shells you opened. +If you answered `y` to the second prompt, you'll have a non-tmux bash +shell open in WSL-B. `exit` to close it. + +### D4. (WSL-A) End the tmux session for cleanup + +The tmux session from D1 is still running and WSL-A is still locked in +its polling loop. Two ways to end it: + +**Option 1 (clean):** From **WSL-B**: + +```bash +ctask attach ctask-053-smoke # rejoins the tmux session via the always-tmux path +exit # exits bash inside tmux → session ends +``` + +WSL-A's polling loop notices the session end after ~3s; finalize runs; +WSL-A's prompt returns. + +**Option 2 (lazy):** Press Ctrl-C in WSL-A. Then in **WSL-B**: + +```bash +tmux kill-session -t $(tmux ls | grep ctask-053-smoke | head -1 | cut -d: -f1) +``` PASS / FAIL: ___ @@ -263,20 +402,22 @@ PASS / FAIL: ___ This is the most important refusal: a missing tmux must not leave a half-initialized workspace on disk. -### M1. Hide tmux from PATH +### M1. (WSL-A) Hide tmux from PATH (carefully) + +We need to remove `/usr/bin` from PATH but keep `~/.local/bin/ctask` +reachable. Run in **WSL-A**: ```bash ORIG_PATH=$PATH -export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/bin$\|/usr/bin' | paste -sd:) -# Verify tmux is gone: -which tmux && echo "ERROR: tmux still on PATH" # Should NOT print "ERROR..." +export PATH="$HOME/.local/bin:/bin" # excludes /usr/bin where tmux lives +which tmux 2>/dev/null && echo "ERROR: tmux still on PATH" +which ctask # should print $HOME/.local/bin/ctask ``` -If your `~/.local/bin/ctask` is no longer on PATH after this, copy it -back: `cp $HOME/.local/bin/ctask ./ctask-temp && export PATH=$PWD:$PATH` -(or just use a full path below). +If `which tmux` finds tmux at e.g. `/bin/tmux` or `~/.local/bin/tmux`, +adjust accordingly — the goal is `which tmux` returns nothing. -### M2. Try `ctask new` with persistent mode +### M2. (WSL-A) Try `ctask new` with persistent mode ```bash ctask new --project --agent bash ctask-053-no-tmux @@ -289,36 +430,45 @@ Error: ctask is configured for persistent sessions, but tmux is not installed. Install tmux: Debian/Ubuntu/WSL: sudo apt install tmux + macOS: brew install tmux ... + +Or bypass persistent mode for this command: + ctask new --direct + +To disable persistent mode: + unset CTASK_SESSION_MODE ``` -### M3. Verify NO workspace was created +### M3. (WSL-A) Verify NO workspace was created ```bash ls "$CTASK_ROOT/projects/" 2>/dev/null -# Expected: empty or only the ctask-053-smoke directory from earlier — NO ctask-053-no-tmux ``` +**Expected:** only `2026-05-08_ctask-053-smoke` (from earlier sections), +NO `ctask-053-no-tmux`. + PASS / FAIL: ___ -### M4. Restore PATH +### M4. (WSL-A) Restore PATH ```bash -export PATH=$ORIG_PATH -which tmux # Expected: /usr/bin/tmux +export PATH="$ORIG_PATH" +which tmux # Expected: /usr/bin/tmux ``` --- ## Native Windows refusal (Step 10) -In a Windows PowerShell window (NOT WSL): +Run in **PS-C** (Windows PowerShell, NOT WSL): ```powershell cd C:\Users\Warren\claude_tasks\ctask go build -o ctask.exe . $env:CTASK_SESSION_MODE = "persistent" -$env:WSL_DISTRO_NAME = $null # Make sure we're not pretending to be WSL +$env:WSL_DISTRO_NAME = $null .\ctask.exe new --no-launch ctask-053-windows ``` @@ -335,10 +485,12 @@ Or bypass persistent mode: ctask new --direct ``` -Exit code: 1. NO workspace created (verify under `%USERPROFILE%\ai-workspaces\`). +NO workspace created (verify under `%USERPROFILE%\ai-workspaces\`). ### --direct under persistent on Windows +Still in **PS-C**: + ```powershell .\ctask.exe new --no-launch --direct ctask-053-win-direct ``` @@ -346,14 +498,13 @@ Exit code: 1. NO workspace created (verify under `%USERPROFILE%\ai-workspaces\`) **Expected:** workspace created with one warning line: `[ctask] warning: --direct bypassing persistent mode (no tmux session exists for this workspace)` -Cleanup: +### Cleanup PS-C state ```powershell -.\ctask.exe delete --force ctask-053-win-direct # if --force exists -# Or remove manually: -Remove-Item -Recurse -Force (Get-ChildItem -Path "$env:USERPROFILE\ai-workspaces\general" -Filter "*ctask-053-win-direct*").FullName +$leftover = Get-ChildItem -Path "$env:USERPROFILE\ai-workspaces\general" -Filter "*ctask-053-win-direct*" -ErrorAction SilentlyContinue +if ($leftover) { Remove-Item -Recurse -Force $leftover.FullName } $env:CTASK_SESSION_MODE = $null -Remove-Item ctask.exe +Remove-Item ctask.exe -ErrorAction SilentlyContinue ``` PASS / FAIL: ___ @@ -362,17 +513,23 @@ PASS / FAIL: ___ ## Doctor output (Step 11) -In WSL with the smoke-test root still set: +Run in **WSL-A**: ```bash unset CTASK_SESSION_MODE ctask doctor 2>&1 | grep -E "Session mode|tmux" -# Expected: [INFO] Session mode: direct (tmux not required) +``` +**Expected:** `[INFO] Session mode: direct (tmux not required)` + +```bash CTASK_SESSION_MODE=persistent ctask doctor 2>&1 | grep -E "Session mode|tmux" -# Expected: -# [INFO] Session mode: persistent -# [INFO] tmux found: tmux 3.5a (/usr/bin/tmux) +``` + +**Expected (two lines):** +``` +[INFO] Session mode: persistent +[INFO] tmux found: tmux 3.5a (/usr/bin/tmux) ``` PASS / FAIL: ___ @@ -381,6 +538,8 @@ PASS / FAIL: ___ ## Cleanup (Step 12) +In **WSL-A** (or WSL-B, doesn't matter): + ```bash unset CTASK_SESSION_MODE # Kill any leftover tmux sessions from smoke testing: @@ -392,11 +551,13 @@ unset CTASK_ROOT rm -f ~/.local/bin/ctask ``` +In **PS-C** (already cleaned up at end of section 10). + --- ## Reporting back -When complete, report PASS/FAIL for each labeled section back to me. If -all pass, I'll merge the branch to `main` and you can `just install` and -optionally `git tag v0.5.3`. If any fail, paste the exact output and I'll -investigate. +When complete, paste me PASS/FAIL for each labeled section. If everything +passes, I'll merge `feat/v0.5.3-persistent-session-mode` to `main` and +you can `just install` and `git tag v0.5.3`. If anything fails, paste the +exact output and I'll investigate.