fix(dashboard): inherit spawning session folder for Start in new worktree - #9046
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS A one-call-site omission fixed at the call site, with the fallback pinned and no new surface — the proportionate shape for this bug. [DESIGN-REVIEWED] e6d0ae1 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe change threads No findings. [OPUS-REVIEWED] e6d0ae1 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All evidence checked: the First-Principles-Verdict: PASS One omitted optional argument was the whole defect; the fix threads it through the existing parameter, adds no surface, and pins both directions. What this change shipsIntent: a worktree session opened from a filed session lands in that same sidebar folder — a FIX.
Watch
[FIRST-PRINCIPLES-REVIEWED] e6d0ae1 |
UX Review (Fable 5) — ✅ PASSUX-level review of UX-Verdict: PASS The only visible change — the worktree session filed under its parent's folder — is exactly what the blind reader saw and correctly understood as a second conversation in that folder. The diff adds no controls or strings; every confusion the blind reader logged (icon cluster, "auto", the pre-filled draft, carried-over messages) is pre-existing surface untouched by this change. Before/after states are screenshotted in both themes, and nothing persistent transforms in place, so no recording is owed. [UX-REVIEWED] e6d0ae1 |
704e76c to
579135a
Compare
579135a to
e6d0ae1
Compare
What is the problem?
Clicking "Start in new worktree" on a
suggest_followupcard opens a new session scoped to the worktree, but that session lands at the top level of the sidebar - even when the session it was spawned from is filed under a project folder. The reporter (@dominik-richter) runs multiple worktrees per project, one session per worktree, so every worktree session had to be dragged into the right folder by hand.In one sentence he would recognise: a worktree session now opens in the same sidebar folder as the session you started it from, instead of at the top level.
Why it matters to the user
For a builder who keeps one folder per project and one session per worktree, the top-level default breaks the grouping on every single worktree they open. The project directory was already resolved correctly - only the sidebar folder was unset - so the sessions looked misfiled while actually being scoped right, and the only remedy was a manual drag each time.
How the fix solves it - symptom to root cause
src/kiro_crew/dashboard/handlers/worktree.pyonly creates the directory and returns{ok, path, branch, ...}; it carries no folder concept (confirmed: zerofolderreferences onmain).followupStartInWorktree(website/src/pages/ChatPage.tsx), which calledcreateSlot({ mode, project: path, activate: false })- with nofolder_id. That omission is the whole bug: a session created without a folder is filed nowhere.createSlot/session_createaccept afolder_id, andcreateSlotalready forwards it toapi.createChatSlot.currentSlot(the object that supplies.projecttwo lines up) carriesfolder_id. The fix readscurrentSlot?.folder_idand passes it through the existing argument. When the spawning session is itself unfiled,folder_idisundefined, so the new session lands top-level - exactly today's behaviour. No default folder is invented; unfiled stays unfiled, and no new API surface is added.The
blockedlabel on #6347 is stale: it was applied 2026-08-27 with the sole unblock condition "PR #6146 merges. No other action taken." #6146 merged 2026-09-01, so the blocker has expired - noting it here so nobody re-applies the label. Credit to @iamwhatever's triage comment, which prescribed this exact fix ("read the spawning session's folder in the worktree handler and pass it through the new argument, with the fallback staying top-level when the spawning session is unfiled") and confirmed the premise by measuring that the handler had no folder reference.What tests we did
website/src/test/ChatPageFollowup.test.tsx- added two pins, run withvitest run(single file, no watch):folder-proj; assertsapi.createChatSlotis called with that folder in itsfolder_idpositional arg. Mutation-verified: removingfolder_id: originFolderIdfrom thecreateSlotcall reddens exactly this test withAssertionError: expected undefined to be 'folder-proj', the fallback case still passes, and every unrelated case still passes; reverting restores green.ChatPageW3Coverage.test.tsx(48 tests) also exercises the worktree flow - still green (56 tests across both files).tsc --noEmitclean.Screenshots
Before - the spawning session alone under the KiroCrew folder (count 1):
After - "Start in new worktree" opens
feat/ws-rate-limitnested under the same KiroCrew folder (count 2), not at the top level:Light-theme parity is in the same directory (
01-origin-filed-light.png,02-worktree-session-inherits-folder-light.png). Reviewers can also see the change in the Files changed tab.Other suggestions
@iamwhatever's triage raised whether folder inheritance should apply to every agent-initiated session or only the worktree button. This PR takes the narrower, cheaper default the issue offered as its baseline (inherit on the worktree path, no new API surface, no target-folder override). Broadening it to other session-creation paths, or adding an explicit override, would be a separate change and a separate decision.
Pattern harvest
Defect class: a capability's plumbing existed end to end (
createSlotacceptedfolder_id, the server honoured it) but one call site omitted the optional argument, so the feature silently defaulted to "unfiled". The bug was invisible to a symbol search -folder_idis present all around the call - and only visible by reading what the one caller actually passed.Rule candidate: when a fix is "thread an existing optional argument through one more caller", pin it with a test that (a) populates the argument on the INPUT and (b) asserts the value reaches the boundary call by its stable identity, then mutation-verify by deleting the argument - an assertion that would pass with the argument absent is vacuous. A positive-only assertion ("a folder was passed") is not enough; pair it with the unfiled complement so the fallback is pinned too.
Closes #6347