Skip to content

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

@anshulsao

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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions