Skip to content

bug(do): resume loses prior context when bound session's cwd != task.work_dir #59

Description

@anshulsao

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:

  1. 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.

  2. 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

  1. 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.
  2. Resume uses session_cwd. cmdDo resume path: cwd := session_cwd when non-empty, else fall back to task.work_dir. Bootstrap path unchanged.
  3. 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).
  4. 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.gocmdDoHere UPDATE (line ~640), bootstrap UPDATE (line ~259), resume cwd selection (line ~318)
  • internal/flowdb/db.gosession_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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions