docs(v0.5.3): smoke-test checklist v2 -- explicit terminals and corrected expectations

This commit is contained in:
2026-05-08 15:58:26 -04:00
parent aee3a20012
commit 659e318535
@@ -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-<hash> 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-<hash>` 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 <host> ctask resume <workspace>`
- `ctask resume <workspace> --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 <workspace> --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-<hash>
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 <workspace> --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 <workspace> --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.