Summary
flow do <task> (with or without --with) silently loses prior conversation context when the bound session's jsonl was originally written under a cwd different from the task's work_dir. Flow always spawns the resume tab at task.work_dir; claude --resume <uuid> looks for the jsonl under that encoded cwd; if it isn't there, claude either errors (No conversation found with session ID...) or — depending on workspace-trust state — silently falls back to a fresh session. The user sees a Claude tab open against the same session_id but with no memory of prior messages.
Originally surfaced as "flow do --with doesn't open the same session_id", but --with is incidental — the same break happens on plain flow do <task>. --with just makes it visible because the injected first message is the only thing in the (fresh) transcript.
Reproduction (isolated $FLOW_ROOT)
mkdir -p /tmp/repro/cwd-A /tmp/repro/cwd-B
SID=$(uuidgen | tr A-Z a-z)
# 1. Create a real claude session at cwd-A
cd /tmp/repro/cwd-A
claude --session-id "$SID" -p "remember the secret word VERMILLION"
# jsonl lands at ~/.claude/projects/-tmp-repro-cwd-A/$SID.jsonl
# 2. Create a flow task with work_dir = cwd-B, then bind the cwd-A session to it
FLOW_ROOT=/tmp/repro/flow-root flow init
FLOW_ROOT=/tmp/repro/flow-root flow add task "Mismatch" --slug mismatch --work-dir /tmp/repro/cwd-B
sqlite3 /tmp/repro/flow-root/flow.db \
"UPDATE tasks SET status='in-progress', session_id='$SID',
session_started=datetime('now'), status_changed_at=datetime('now')
WHERE slug='mismatch'"
# 3. What `flow do mismatch --with "..."` effectively runs:
cd /tmp/repro/cwd-B && claude --resume "$SID" "what was the secret word?" -p
# → "No conversation found with session ID: <uuid>"
# Same command from the cwd the session actually lives at works:
cd /tmp/repro/cwd-A && claude --resume "$SID" "what was the secret word?" -p
# → "The secret word is VERMILLION."
In interactive (non--p) mode on a trusted workspace, claude may silently start a fresh conversation instead of erroring — that's the "lost context" symptom the user reports.
Root cause (internal/app/do.go)
Two interacting facts:
-
cmdDoHere (around do.go:638) writes session_id but never captures os.Getwd() into session_cwd. The session_cwd column exists in the schema (and is referenced in pr-58's harness refactor) but is never populated by main's code path. So a --here bind from a session originally started outside the task's work_dir records the session_id without anchoring the cwd it was created at.
-
cmdDo resume path uses cwd := task.WorkDir (do.go:~318) — it never consults any per-session cwd. So flow do always spawns at task.work_dir, even when the bound session's jsonl lives elsewhere.
How users land in this state: free claude started from ~, then flow do --here <task> where the task's work_dir is some repo path. Subsequent flow do <task> (or flow do <task> --with ...) breaks silently.
Bootstrap (no prior session_id) is unaffected — it allocates a fresh UUID and spawns at task.work_dir, so the jsonl lands at the same encoded cwd that future resumes will use.
Fallout in the wild
Audit of one user's ~/.flow/flow.db (38 in-progress tasks, alpha.14) found ≥6 tasks with session jsonls at a wrong-cwd project directory, and several more where the jsonl is missing entirely. Concrete examples (after filtering out .-encoding false positives):
praxis-memory work_dir=.../praxis-cli jsonl_dir=-Users-<u>
qs-ir-cards work_dir=.../agent-factory jsonl_dir=-Users-<u>
script-modules-as-engine work_dir=.../design-docs jsonl_dir=-Users-<u>
anthropic-radar--2026-... work_dir=.../facets/anthropic jsonl_dir=.../facets (parent)
x-account--2026-05-20-... work_dir=.../x-account/workspace jsonl_dir=.../daily-triage/workspace
schedule-ci-autofix work_dir=.../agent-factory jsonl_dir=-Users-<u>
The "parent-dir" and "wrong-playbook" rows suggest the same bug, plus possibly a separate path-shift in playbook-run binding.
Suggested fix shape
- Capture cwd at bind time.
cmdDoHere UPDATE adds session_cwd = ? with os.Getwd(). Bootstrap path UPDATE adds session_cwd = task.work_dir. Both already know the right value.
- Resume uses session_cwd.
cmdDo resume path: cwd := session_cwd when non-empty, else fall back to task.work_dir. Bootstrap path unchanged.
- One-shot backfill / heal command. For existing rows with NULL
session_cwd: scan ~/.claude/projects/*/ for each task's session_id, set session_cwd to the decoded path of the directory containing the jsonl (or NULL if not found / multiple matches).
- Decide on
--here cwd-mismatch policy — silently bind and trust the captured session_cwd, or warn when os.Getwd() != task.work_dir at --here time. Probably warn but proceed; users sometimes legitimately bind across dirs.
Step 3 is the only piece that touches existing user data; everything else is a forward-only fix.
Files
internal/app/do.go — cmdDoHere UPDATE (line ~640), bootstrap UPDATE (line ~259), resume cwd selection (line ~318)
internal/flowdb/db.go — session_cwd already in schema, no migration needed
internal/app/do_test.go — tests for the four scenarios: bootstrap (writes work_dir), --here (writes os.Getwd), resume with session_cwd set (uses it), resume with session_cwd null (falls back)
- new
internal/app/heal.go (or similar) — backfill subcommand or auto-heal hook
Notes
- The injection text and command construction for
--with are correct — verified against the existing TestCmdDoWithResumeAppendsPositionalArg. The bug is entirely in the cwd resolution layer above it.
- Claude CLI behaviour on
--resume <uuid> with mismatched cwd is not flow's bug to fix; flow's job is to spawn from the right cwd.
Summary
flow do <task>(with or without--with) silently loses prior conversation context when the bound session's jsonl was originally written under a cwd different from the task'swork_dir. Flow always spawns the resume tab attask.work_dir;claude --resume <uuid>looks for the jsonl under that encoded cwd; if it isn't there, claude either errors (No conversation found with session ID...) or — depending on workspace-trust state — silently falls back to a fresh session. The user sees a Claude tab open against the samesession_idbut with no memory of prior messages.Originally surfaced as "
flow do --withdoesn't open the same session_id", but--withis incidental — the same break happens on plainflow do <task>.--withjust makes it visible because the injected first message is the only thing in the (fresh) transcript.Reproduction (isolated
$FLOW_ROOT)In interactive (non-
-p) mode on a trusted workspace, claude may silently start a fresh conversation instead of erroring — that's the "lost context" symptom the user reports.Root cause (
internal/app/do.go)Two interacting facts:
cmdDoHere(arounddo.go:638) writessession_idbut never capturesos.Getwd()intosession_cwd. Thesession_cwdcolumn exists in the schema (and is referenced in pr-58's harness refactor) but is never populated bymain's code path. So a--herebind from a session originally started outside the task'swork_dirrecords the session_id without anchoring the cwd it was created at.cmdDoresume path usescwd := task.WorkDir(do.go:~318) — it never consults any per-session cwd. Soflow doalways spawns attask.work_dir, even when the bound session's jsonl lives elsewhere.How users land in this state: free
claudestarted from~, thenflow do --here <task>where the task'swork_diris some repo path. Subsequentflow do <task>(orflow do <task> --with ...) breaks silently.Bootstrap (no prior session_id) is unaffected — it allocates a fresh UUID and spawns at
task.work_dir, so the jsonl lands at the same encoded cwd that future resumes will use.Fallout in the wild
Audit of one user's
~/.flow/flow.db(38 in-progress tasks, alpha.14) found ≥6 tasks with session jsonls at a wrong-cwd project directory, and several more where the jsonl is missing entirely. Concrete examples (after filtering out.-encoding false positives):The "parent-dir" and "wrong-playbook" rows suggest the same bug, plus possibly a separate path-shift in playbook-run binding.
Suggested fix shape
cmdDoHereUPDATE addssession_cwd = ?withos.Getwd(). Bootstrap path UPDATE addssession_cwd = task.work_dir. Both already know the right value.cmdDoresume path:cwd := session_cwdwhen non-empty, else fall back totask.work_dir. Bootstrap path unchanged.session_cwd: scan~/.claude/projects/*/for each task'ssession_id, setsession_cwdto the decoded path of the directory containing the jsonl (or NULL if not found / multiple matches).--herecwd-mismatch policy — silently bind and trust the capturedsession_cwd, or warn whenos.Getwd() != task.work_dirat--heretime. Probably warn but proceed; users sometimes legitimately bind across dirs.Step 3 is the only piece that touches existing user data; everything else is a forward-only fix.
Files
internal/app/do.go—cmdDoHereUPDATE (line ~640), bootstrap UPDATE (line ~259), resume cwd selection (line ~318)internal/flowdb/db.go—session_cwdalready in schema, no migration neededinternal/app/do_test.go— tests for the four scenarios: bootstrap (writes work_dir),--here(writes os.Getwd), resume with session_cwd set (uses it), resume with session_cwd null (falls back)internal/app/heal.go(or similar) — backfill subcommand or auto-heal hookNotes
--withare correct — verified against the existingTestCmdDoWithResumeAppendsPositionalArg. The bug is entirely in the cwd resolution layer above it.--resume <uuid>with mismatched cwd is not flow's bug to fix; flow's job is to spawn from the right cwd.