Skip to content

feat(worktree): manage git worktrees for any repository from chat - #3139

Closed
nathanyi96 wants to merge 1 commit into
kirodotdev:mainfrom
nathanyi96:ny/general-worktree
Closed

feat(worktree): manage git worktrees for any repository from chat#3139
nathanyi96 wants to merge 1 commit into
kirodotdev:mainfrom
nathanyi96:ny/general-worktree

Conversation

@nathanyi96

@nathanyi96 nathanyi96 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Dev Fleet could already create, inspect and reap git worktrees, but only for Kiro Crew's own
checkout. Three module-level constants pinned it there: the repository path, the base branch
"main", and a Kiro Crew-specific provisioning step (venv + npm + dist).

Every other repository was unreachable. A user with several chat sessions open on one project had
them all editing the same working tree — overwriting each other's files, fighting over which branch
was checked out, and racing each other's build output.

Why it matters

Parallel sessions on one repository is the normal way this product gets used, and today it is
actively unsafe: two sessions asked to work on different features will silently clobber one
another. The plumbing to fix it already existed and was one repository away from being general.

Fix (symptoms → root cause → change)

Symptom: worktree management works for Kiro Crew and nothing else.
Root cause: repository and base ref were constants, not parameters, and the flow
was welded to a Kiro Crew-specific provisioning step.
Change: repository and base ref become parameters; provisioning is dropped from
this path rather than parameterized — a general worktree needs no venv or dist, and
Dev Fleet keeps its own on-ramp for the cases that do.

  • Repository is now a request parameter, constrained to directories a chat slot is already
    bound to. match_allowed_root compares normalized strings before any filesystem call
    and returns the value from the allow-list, never the caller's, so every downstream path
    operation runs on a path the server chose. The two halves of that list do not have the same
    provenance — see the allowed_repo_roots docstring, which states which half is a
    shape-checked caller string and the two conditions that make it sound.
  • Base ref is detected per repository from origin/HEAD (falling back to HEAD) instead of
    assuming main.
  • Provisioning is optional and defaults to nothing, so a tree on a repository with no setup
    step is usable the moment git returns.

One session binds to one worktree and one branch. A session enters a tree (its project becomes
that tree) and leaves back to the main checkout. Trees are created as siblings of the
repository rather than nested inside it, so no .gitignore entry is needed to keep them out of the
repository's own status.

The agent is told which tree it is in

Isolating a session's files is only half the job: the agent still saw a bare directory path and
had no way to know it was on a feature branch, so nothing stopped it from doing one ticket's work
while sitting in another ticket's tree. The [PROJECT] context block now carries a git line:

Git: branch `feat/docs-links` in a linked worktree of /path/to/repo. Keep this
session's work on this branch — sibling worktrees of the same repository hold
other branches.

Three properties, each load-bearing:

  • Pure filesystem. A bounded upward walk plus two small reads — no git subprocess, because this
    runs inline on every prompt assembly and a spawn per turn is not affordable. A linked worktree's
    .git is a file holding a gitdir: pointer where the main checkout's is a directory, which
    is both how the two are told apart and how the linked tree's HEAD is located. An unreadable or
    unexpected layout emits nothing rather than a guessed branch: a wrong branch name would send work
    to the wrong branch, while no line leaves the agent exactly as informed as it was before.

  • Read from disk, not from the stored binding. The line is derived from the directory rather
    than from slot.worktree, so a git checkout made in a terminal is reflected too. It tracks the
    state, not this product's own actions.

  • A change is announced, not swapped silently. ContextBuilder._last_git_line holds the last
    line injected per session key (bounded, cleared wholesale). When a turn's reading differs, both
    sides are named:

    ⚠ WORKTREE SWITCHED since your last turn — your working directory moved with it:
        before: Git: branch `main` in the main checkout of /path/to/repo.
        now:    Git: branch `feat/docs-links` in a linked worktree of /path/to/repo.
    Work in the previous checkout is untouched; nothing was moved or lost. Treat files
    you read earlier as belonging to the previous tree and re-read anything you rely on.
    

    An agent that sees only the new branch has to infer the switch, and inferring it wrongly puts
    one ticket's work on another's. Keying on the session key matters because a project change tears
    down and cold-starts the provider — the session key survives that, so the switch is still
    announced on the restarted turn. A session with no previous reading never claims a switch.

Recyclability without gh and without network

git cherry compares patch identity, so a squash-merged branch reports zero patch-unique
commits while rev-list still counts them. That combination (own_commits > 0 and ahead == 0) is
what identifies landed work.

The base is resolved to a concrete commit in the main repository before any comparison. A ref
name re-resolves inside each worktree against that worktree's own tip, which makes every count
zero and reads unmerged work as landed. This was a real bug, found and fixed before the tests were
written.

Every uncertain state fails closed and is never recyclable:

condition verdict recyclable
is_main active no
dirty check unreadable dirty_check_failed no
base unresolvable base_unknown no
own commits, none patch-unique, clean merged yes
same but dirty merged_dirty no
no own commits, clean, > 48h empty yes
no own commits, clean, younger fresh no

Branch refs are never deleted, on any path — a leftover branch costs bytes, a wrongly deleted
one costs commits. A dirty tree is refused with 409 unless force is set: uncommitted work exists
only inside that directory, so destroying it is the user's decision and the flag is the record that
they made it.

Off by default

The feature sits behind dashboard.worktrees_enabled (default false), so an instance opts in
before any control appears. Both new endpoints return 403 when it is off — hiding the control would
leave them reachable by anything that already knows the URL. POST /api/worktree/create is
deliberately not gated, because the follow-up card's worktree action predates the flag and a
default-off gate would regress it for instances that never opt in.

Performance

Listing 4 trees on a 185 MB repository, measured:

before after
cold, with sizes 3831 ms 1836 ms
cold, no sizes 1306 ms
cached 73 ms

A sandboxed git spawn costs 100–150 ms, so the win is spawn count: branch and head come from the
porcelain listing instead of two extra rev-parse calls per tree, one
rev-list --count --left-right base...HEAD replaces two calls (left = behind, right = ahead —
the order is load-bearing), per-tree probes run concurrently, and the listing is cached for 10 s
with an explicit ?fresh=1 bypass and invalidation after a removal.

Tests

  • test/test_worktree_fleet.py (new, 54 tests) — TestPruneVerdict, TestListWorktrees,
    TestRemoveWorktree, TestFeatureFlag, TestAccessBarrier, TestListCaching,
    TestCommitCounts, TestFindWorktree, TestListEndpoint, TestRemoveEndpoint,
    TestWorktreeBindingSanitizer. The repo fixture builds a real bare origin with
    remote set-head origin main so origin/HEAD resolves — without it the interesting verdicts
    cannot be told apart. The flag fixture patches KiroCrewConfig rather than worktrees_enabled,
    so the real gate function stays under test. Two of these cover the barrier's second pass, where
    a granted subdirectory resolves upward to a repo root that was never granted, and where that
    resolved root is itself a sensitive path — distinct refusals from the ones on the submitted path,
    and each carries its own machine-readable code.
  • test/test_project_git_line.py (new, 17 tests) — split in two, because the reading and the
    announcement fail for different reasons. The reading half fabricates the layouts git itself
    writes (a .git directory for the main checkout, a .git file with a gitdir: pointer for a
    linked worktree, absolute and relative pointers) so it needs neither git nor a sandbox, and
    asserts silence for a parent directory of a repository, a non-repo, a missing path, a malformed
    pointer, a corrupt HEAD, and a hand-crafted ref trying to append prompt lines. The announcement
    half drives build_message and asserts the transition itself: a first turn never claims a
    switch, an unchanged turn stays quiet, entering a worktree names both sides, a branch change
    under the same path is announced too, one session's switch is not announced to another, and the
    per-session tracking stays bounded.
  • test/test_worktree_create.py (81 tests) — retargeted to the extracted git_exec module;
    all still pass, which is what proves the extraction is a move and not a rewrite.
  • website/src/test/ProjectPicker.test.tsx — 8 new tests covering the opt-in footer: absent
    without a handler, checked/unchecked rendering, that flipping it reports the value without
    committing a directory selection or closing the picker, the amber non-repo verdict replacing
    the description, that the switch stays operable on a non-repo directory (the setting is
    instance-wide), that no verdict is stated while the probe is undetermined, and that
    aria-describedby points at the verdict line.

Manual verification

Verified end-to-end on an isolated pod (port 7842) against a real third-party repository
(multica, 185 MB, Go + React monorepo) with four worktrees — not against Kiro Crew itself, since
the whole point is that it now works elsewhere.

Confirmed by hand: flag off → 403 and no control; PATCH to on → 200 and the control appears
without a reload; off again → 403, with no restart between flips. Enter → binding set, project
moves to the tree; Exit → binding cleared. From inside a tree the listing still returns all four
entries (this caught a 403 the allow-list would otherwise cause). The recent-projects picker is not
polluted by tree paths. Squash-merge detection was checked against real squash-merged branches in
that repository.

The git line's assumptions were checked against that repository's real .git files rather than only
the fabricated fixtures: git writes gitdir: <absolute path> and ref: refs/heads/feat/docs-links,
which confirms both the pointer form and the branch-name-containing-a-slash case that a naive
split("/")[-1] would corrupt.

Screenshots

Captured on the pod against multica, the third-party repository — the surfaces are shown doing the
thing the code claims, not just existing.

The opt-in, in the project picker, on a directory that IS a repository. The switch is on, so the
shelf gained ⎇ Worktrees to the right of the directory chip. Turning it off hides that control and
refuses its endpoints; no worktree on disk is touched either way.

Project picker footer with the worktree-sessions switch on, and the Worktrees control present on the shelf

The same footer on a directory that is NOT a repository (KiroCrew-WS, the folder above the
checkout). It states why instead of offering a switch whose effect would be invisible, and the shelf
control is absent. The switch itself stays operable because the setting is instance-wide — a
non-repo directory explains itself rather than holding a global setting hostage.

Project picker footer showing an amber not-a-git-repository verdict, with no Worktrees control on the shelf

The panel from the main checkout. The main checkout is the first row, marked this session, and
its trash affordance is disabled. Each linked tree carries its dirty state, ahead/behind counts,
size, and verdict chip (active / fresh).

Worktrees popover opened from the main checkout, listing three linked worktrees with verdicts

The same panel after entering feat/docs-links. this session moved to that row and its trash
is now disabled instead; the main-checkout row grew the exit affordance; the shelf and the sidebar
chip both follow the session into the tree.

Worktrees popover from inside a linked worktree, with the main checkout row offering the way back

The images are committed under temp-screenshots/worktree-sessions/ and the URLs are pinned to the
commit, so they survive a later force-push or branch deletion.

Notes for reviewers

  • src/kiro_crew/dashboard/handlers/worktree.py shows −245 lines. That is a move, not a
    deletion: the primitives went to src/kiro_crew/worktree/git_exec.py and the original file
    imports them back under private aliases, so its endpoint and every caller keep the same
    behaviour. Two test files follow the primitives to their new home rather than the production
    code keeping a second copy: test_worktree_create.py already targets git_exec, and
    test_dashboard_worktree_coverage.py now patches git_exec for the names that moved
    (sandboxed_spawn_argv, subprocess, GIT_TIMEOUT, SANDBOX_MODE, HOOKS_SINK,
    SANDBOX_LAUNCHER_PREFIX, run_git, repo_lock) while still patching the handler for the
    names the handler still owns (_create_worktree_sync, _worktree_branches, _git_toplevel,
    ...). Its git fixture binds the fake on both modules, because handler-owned helpers
    resolve _run_git through the handler's namespace while git_toplevel / resolve_base_ref
    resolve run_git through git_exec's — patching one and not the other leaves the other
    reaching a real git. The alternative, keeping a second copy of the spawn logic in the handler so
    those patches kept working unchanged, was rejected: it would put the sandbox mode, hooks sink,
    terminal-prompt suppression and launcher-refusal detection in two places that then drift, and
    would leave the old endpoint and the new ones on different spawn implementations.
  • Adding one dashboard config boolean required registering it in three places —
    config/loader.py (field), handlers/core.py _EDITABLE (PATCH path), and
    handlers/files.py _allowed + write + GET (PUT path, which the Settings toggle uses).
    Missing the third returns 400 Unknown fields, which surfaces as a failed save rather than
    anything obviously wrong.
  • Every user-visible string in the panel goes through i18nT under
    components.worktreePanel.*, and sizes through fmtBytes rather than a hand-rolled MB/GB
    concatenation. The verdict chips resolve their key per render rather than from a module constant,
    because a constant captures whichever locale was active at import and then never follows a
    language switch.
  • The awareness behaviour is documented in docs/system-specs/modules/memory-skills-hooks.md
    (Context Builder section), in the same commit.

no issue closed: this generalizes existing behaviour and has no filed issue.

@nathanyi96
nathanyi96 requested a review from a team August 12, 2026 22:31
@nathanyi96
nathanyi96 requested a review from a team as a code owner August 12, 2026 22:31
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Aug 12, 2026
@nathanyi96
nathanyi96 marked this pull request as draft August 12, 2026 22:34
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 12, 2026
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from d88ab96 to faa87e5 Compare August 12, 2026 23:42
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 12, 2026
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from faa87e5 to a25870b Compare August 13, 2026 00:06
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 13, 2026
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from a25870b to 84f22a0 Compare August 13, 2026 00:30
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 13, 2026
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from 84f22a0 to 45b9f38 Compare August 13, 2026 00:53
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Aug 13, 2026
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from f60d893 to 37a3f73 Compare August 13, 2026 08:46
@nathanyi96

Copy link
Copy Markdown
Contributor Author

Review dispositions — 37a3f736 (was f60d893d)

Every finding from all four lanes, with what happened to it. Two were reproduced
with a mutation check (the fix reverted, the new test observed to fail, the fix
restored) rather than assumed.

GPT 5.6 — 5 BLOCKING, 3 FINDING

1. access.py:50 — client-supplied repository in the allow-list — REBUTTED (behaviour), FIXED (documentation)

Not a privilege boundary crossing, for the reasons Opus 4.8 independently reached
when it dropped this same candidate
:

  • the only writer of worktree.repo is the trusted dashboard caller, who can
    already name any non-sensitive directory as a slot project, so admitting the
    string raises no ceiling it did not already have (the MCP set_project path
    does not carry the field);
  • is_sensitive_path is re-applied at operation time to the resolved repo
    root (worktree_fleet.py:131) and to the git toplevel (:178), so the
    sensitive-path control sits where git actually runs;
  • match_allowed_root returns the allow-listed value, never the caller's string.

Design Review's version of this — that the docstring was false — is correct and
is fixed. It claimed the value was "not a value a request can introduce"; it now
states plainly that this half of the barrier carries a shape-checked caller string,
and records the two conditions that make that sound, so future code cannot inherit
a stronger assumption than the barrier provides.

2. chat_persistence.py:1813 — cleared binding returns after restart — FIXED, with a different root cause than proposed

The proposed fix (always write the key) treats the symptom. The cause is that
worktree was never registered in SLOT_OWNED_META_KEYS, so
carry_unowned_metadata classified the absent key as another layer's durable
state and copied the stale binding forward. Fixed by declaring ownership next to
project, which is the field it is written with.

Locked by test_leaving_a_worktree_clears_the_binding_on_disk. Mutation-checked:
with the registration removed the test fails with "a left worktree came back after
the save".

3. worktree_fleet.py:276 — truthy non-boolean authorises the destroy — FIXED

Now body.get("force") is True. Mutation-checked, and the old code was worse than
described: sending {"force": "false"} returned {"ok": true} and deleted the
tree with its uncommitted file
. Locked by
test_truthy_non_boolean_force_does_not_authorize_the_delete, which covers
"false", "0", 1, {}, [], "no".

4. worktree_fleet.py:248 — list omits the allowed SEL event — FIXED

The endpoint audited its six refusals and nothing else, so the log recorded only
requests that were stopped. It now logs outcome="allowed" with the root and tree
count, matching what worktree_remove already did.

5. ChatSidebar.tsx:2557 — extra stacked session-row line — FIXED

Confirmed as a real blocking: true AUTOSDE hit (session-row-fixed-height, whose
file-patterns names this file). The chip now rides the agent/meta line as
trailing detail, per that rule's own Good example, and the extra row is gone.
ChatSidebar.worktreeChip.test.tsx asserts the placement
(chip.closest('.session-agent-label')), not just visibility — a
visibility-only assertion would pass with the forbidden row still present.

6. WorktreePanel.tsx:138 — manual useEffect fetching instead of React Query — ACCEPTED AND DEFERRED

Legitimate, and out of scope here: the panel would need its cache, invalidation and
mutations restructured, in a PR already carrying this much. Filed as follow-up.

7. text-[9.5px] below the 10px minimum — FIXED (4 occurrences → text-[10px]).

8. amber literal bypasses the warning token — FIXED — now text-warn, i.e. the
same root cause as the panel's phantom classes below.

Opus 4.8 — 1 FINDING (no blocking)

Phantom success/warning classes — FIXED. Confirmed against
tailwind.config.js: it defines ok/warn/danger/info, and neither
success nor warning exists as a colour key or a CSS var, so Tailwind emitted
no rule and the recyclable/dirty/error states rendered as inherited body text.
Swapped to ok/warn at all six sites. The opacity modifiers (/40, /10) do
work on these, because the config defines them through withAlpha.

Opus's two dropped candidates need no action and are recorded so they are not
re-raised: the synchronous KiroCrewConfig.load() is fingerprint-cached and is the
established convention, and candidate 3 is the access.py item above.

Design Review (Fable 5) — CONCERNS

Allow-list provenance regression — ANSWERED. See GPT 1: the docstring is fixed;
the behaviour is deliberately unchanged with the argument recorded in the code.

A second worktree engine — ACCEPTED AND DEFERRED. Dev Fleet keeps its own
listing/prune/remove and verdict logic. Folding it onto worktree.service is the
right end state and the wrong contents for this PR; filed as follow-up so the
drift is tracked rather than implied.

"Provisioning becomes a parameter" — FIXED in the description. The claim had no
counterpart in the diff; the wording no longer promises a hook that does not exist.

UX Review (Fable 5) — CONCERNS

False amber alarm in the project picker — FIXED. projectIsRepo is also false
for a session with no project, and the verdict rendered regardless of the opt-in,
so a user who had never enabled the beta was told their folder was wrong. Now
gated on worktreesEnabled && projectIsRepo === false, locked by
"stays silent about a non-repo directory until the beta is enabled".

Dead status colours — FIXED. Same fix as the Opus finding.

One trash icon, two behaviours — PARTIALLY ACCEPTED. The disclosure gap is
real and is fixed: the recyclable trash now carries the ok tone instead of the
destructive one, and the behaviour is stated in the aria-label, so it reaches
keyboard and screen-reader users rather than only hover. Declining the other
half — always showing the confirm — on purpose: instant reclaim of a clean,
merged tree is the value this panel exists to deliver, and a confirm on the safe
path would tax the common case to soften a rare surprise. The visual and
accessible-name distinction addresses the surprise without that cost.

Confirm body leaked internals and broke at zero — FIXED. It now uses the
translated verdictLabel() instead of interpolating the raw verdict code, real
i18next plural forms instead of "commit(s)", and a separate line for
ahead === 0 instead of "0 commit(s) have not landed". The plural keys carry
{{count}} in every form and are seeded per locale with exactly the categories
that locale selects (ru gets few/many, ja/ko/zh-CN only other).

No focus management — FIXED. The panel takes focus on open and returns it to
the opener on close. It focuses the dialog itself rather than the first control,
because the first control on some rows is a destructive trash button and landing
there would make Enter-to-dismiss delete a tree.

Two suggestions — ACCEPTED AND DEFERRED (verdict-chip tooltips; picker copy
naming the instance-wide scope). Both are copy/affordance additions with new
strings across 11 catalogs; folded into the same follow-up.

Verification

Frontend 1061 files / 17810 tests, i18n 16/16 with I18N_BASE_REF, tsc -b,
isort, flake8, mypy 945 files, and 357 targeted backend tests — all green.
One pre-existing failure remains and is not from this branch:
App.test.tsx > closes the modal on Escape fails identically on a clean
origin/main checkout and never fails on CI (macOS-only).

@github-actions github-actions Bot added readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 13, 2026
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from 37a3f73 to ffae74d Compare August 13, 2026 09:48
Dev Fleet could already create, inspect and reap git worktrees, but only
for Kiro Crew's own checkout: the repository path, the base branch "main"
and a Kiro Crew-specific provisioning step were module-level constants.
Any other repository was unreachable, so a user with several sessions on
one project had them all editing the same working tree -- overwriting
each other's files, fighting over the checked-out branch, and racing
build output.

Generalize the three constants into parameters. The repository becomes a
request parameter constrained to directories the server already holds for
a chat slot; the base ref is detected per repository from origin/HEAD
rather than assuming "main"; provisioning becomes optional and defaults
to nothing, so a tree on a repository with no setup step is usable the
moment git returns.

One session binds to one worktree and one branch. A session enters a tree
(its project becomes that tree) and leaves back to the main checkout.
Trees are siblings of the repository rather than nested inside it, so no
.gitignore entry is needed to keep them out of the repository's status.

The agent is told which of them it is in: the [PROJECT] context block
names the branch and whether the directory is the main checkout or a
linked worktree. Without it the agent sees a path and nothing more, so it
cannot keep a ticket's work on that ticket's branch. The line is read
from .git and HEAD each turn rather than from the session's stored
binding, so a checkout made in a terminal is reflected too; it is pure
filesystem because prompt assembly cannot afford a git spawn per turn,
and an unexpected layout emits nothing rather than a guessed branch. A
change is announced with both sides instead of swapped silently, since an
agent that sees only the new branch has to infer the switch and inferring
it wrongly puts one ticket's work on another's. The last reading is held
per session key, which outlives the provider restart that a project
change triggers.

Recyclability is judged without gh and without network access. `git
cherry` compares patch identity, so a squash-merged branch reports zero
patch-unique commits while rev-list still counts them; that combination
identifies landed work. The base is resolved to a concrete commit in the
main repository before comparing, because a ref name re-resolves inside
each worktree against its own tip and would make every count zero --
reading unmerged work as landed. Every uncertain state fails closed: an
unreadable dirty check or an unresolvable base is never recyclable, and a
branch ref is never deleted.

The feature is off by default behind dashboard.worktrees_enabled, so an
instance opts in before any control appears. The opt-in is surfaced in
the project picker, next to the directory it applies to, and states when
the chosen directory is not a git repository instead of offering a switch
whose effect would be invisible.

Listing 4 trees on a 185 MB repository went from 3831 ms to 1836 ms cold
and 73 ms cached, by reading branch and head from the porcelain listing
instead of two extra spawns per tree, replacing two rev-list calls with a
single --left-right count, probing trees concurrently, and caching for 10
seconds with an explicit bypass.
@nathanyi96
nathanyi96 force-pushed the ny/general-worktree branch from ffae74d to b8e8faf Compare August 13, 2026 09:50
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Aug 13, 2026
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Aug 14, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

This PR shares DashboardState, _ChatSlot, chat_persistence.py, and session rehydration metadata with #2783 and #4904. The scopes are not duplicates: #2783 prevents app-slot/channel auto-binding, this branch persists general-repository worktree state, and #4904 persists/uses fork-parent state for merge-back.

Please rebase after the #2783 identity-binding floor, keep worktree ownership/path metadata additive rather than replacing session identity fields, and coordinate #4904 afterward. Round-trip rehydration should retain channel/app identity, worktree state, and parent/fork metadata simultaneously.

@bolichen97

Copy link
Copy Markdown
Collaborator

Audit note — part of this has already landed; the rest has not

This PR is not a duplicate and is not finished by anything on main. The audit checked it part by part against main, and some of what it does is already there. Flagging it so a reviewer does not have to rediscover the overlap, and so the PR is not mistaken for fully-covered work.

Which parts main already has

What is still genuinely yours

  • The package itself: git ls-tree -r --name-only origin/main -- src/kiro_crew/worktree returns nothing. No init.py, access.py, git_exec.py, service.py.
  • ALL of worktree/service.py (486 new lines): STALE_EMPTY_AGE_S, VERDICT_MERGED/_MERGED_DIRTY/_EMPTY/_FRESH/_ACTIVE/_DIRTY_CHECK_FAILED/_BASE_UNKNOWN, RECYCLABLE_VERDICTS, WorktreeInfo, RepoWorktrees, _parse_worktree_list, _patch_unique_ahead, _ahead_behind, _dir_size, prune_verdict, inspect_worktree, _worktree_age_s, list_worktrees, _prepare, _inspect_entry, CACHE_TTL_S, _MAX_CACHED_REPOS, invalidate_cache, list_worktrees_cached, find_worktree, remove_worktree, repo_disk_bytes. git grep -l over origin/main returns 0 files for every one of these tokens (the two names that do hit — find_worktree, invalidate_cache — hit only unrelated Dev Fleet / history-cache code).
  • handlers/worktree_fleet.py (344 new lines): GET /api/worktree/list, POST /api/worktree/remove, _Refused, WORKTREES_OFF, worktrees_enabled(), deny_when_disabled(), _resolve_repo_root() with its two-pass barrier (submitted path AND resolved git toplevel), _body_repo. File absent from origin/main.
  • Every new machine-readable error code: worktrees_disabled, repo_not_allowed, repo_root_not_allowed, repo_not_a_directory, repo_sensitive_path, root_sensitive_path, not_a_git_repository, repo_required, repo_and_path_required, git_timeout, list_failed, remove_failed, sandbox_unavailable (on these endpoints), invalid_worktrees_enabled. git grep -F worktrees_disabled origin/main and repo_root_not_allowed => 0 files.
  • Route registration in dashboard/routes/chat.py for /api/worktree/list and /api/worktree/remove. git grep -F 'api/worktree/list' origin/main => 0 files. (api/worktree/remove hits only Dev Fleet's OWN app endpoint in apps/builtins/dev_fleet/server.py, a different, Kiro-Crew-checkout-only surface.)
  • The config key dashboard.worktrees_enabled in all four registration places: config/loader.py DashboardConfig field + the _safe_bool line in load(); handlers/core.py _EDITABLE entry; handlers/files.py _allowed set + bool validation + GET payload; website settingsRegistry.gen.ts; settings/ChatPanel.tsx; chat/ChatSettings.tsx DashboardConfig type. git grep -F worktrees_enabled origin/main => 0 files. main's files.py:3721 _allowed set reads ... "auto_open_git_panel", "folder_suggestions_enabled", "session_card_source_links"} — no worktrees_enabled (and note main has since added use_builtin_browser, auto_open_git_panel, session_card_source_links to that one physical line, so the PR's version of it is stale).
  • The session binding: "worktree" in _ChatSlot.slots / self.worktree: dict[str, str] = {} in init / "worktree": dict(self.worktree) in to_dict. origin/main:src/kiro_crew/dashboard/state.py:2982 shows slots going straight from "project" to "created_at".
  • "worktree" in history.py SLOT_OWNED_META_KEYS. origin/main:src/kiro_crew/history.py:166-194 lists 24 keys and worktree is not among them.
  • chat_handlers.py _sanitize_worktree_binding, the project endpoint accepting an optional worktree object, the if project and not slot.worktree recent-projects exclusion, and worktree in the response. git grep -F _sanitize_worktree_binding origin/main => 0 files.
  • chat_persistence.py: the stored_worktree field-allow-listed restore in BOTH _rehydrate_slot_from_history and _restore_recent_sessions_steps, plus meta_line["worktree"] in _save_slot_to_history. git grep -F 'slot.worktree' origin/main => 0 files.
  • The whole prompt-awareness half in context.py: _GIT_ROOT_WALK_LIMIT, _GIT_LABEL_CAP, _MAX_GIT_LINE_SESSIONS, _project_git_line (the pure-filesystem .git/HEAD reader, gitdir: pointer handling, detached-HEAD branch, one-readline prompt-injection bound), ContextBuilder._last_git_line, the restructure of the [PROJECT] append into block = (...) + parts.append(block + "\n"), and the ⚠ WORKTREE SWITCHED since your last turn announcement. origin/main:src/kiro_crew/context.py:2994-2999 still holds the unmodified five-line parts.append(f"[PROJECT] Active project directory: {project}\n" ... "when answering questions.\n\n"). git grep on origin/main for _project_git_line, _last_git_line, WORKTREE SWITCHED, Git: branch, _GIT_LABEL_CAP => 0 files each. main has filesystem HEAD readers elsewhere (apps/registry.py:4462+, apps/builtins/md_notebook/git_ops.py:441/458, ops_mission_control/backend/ledger_sync.py:167, auto_improvement/spine/git_safety.py:144) but NONE of them injects anything into a prompt — a reuse opportunity, not coverage.
  • Frontend: website/src/components/WorktreePanel.tsx (527 new lines) absent from origin/main's tree; api.worktreeList / api.worktreeRemove in api/client.ts (main:2464-2468 has only the follow-up card's createWorktree -> POST /api/worktree/create); the ⎇ Worktrees shelf control in ChatInput.tsx; the ProjectPicker.tsx opt-in footer with its not-a-repo verdict; the ChatSidebar.tsx branch chip; ChatPage.tsx wiring; chatSlice.ts; types/index.ts. git grep -i worktree over all nine of those files on origin/main returns only feat(chat): agent-suggested follow-ups as a card above the composer #461's follow-up-card create path and unrelated comments.
  • i18n: components.worktreePanel.* keys across 13 locale files + en.manual.json + pluralKeys.json. git grep -F worktreePanel origin/main => 0 files.
  • Tests: test/test_worktree_fleet.py (637 new lines, 11 classes: TestPruneVerdict, TestListWorktrees, TestRemoveWorktree, TestFeatureFlag, TestAccessBarrier, TestListCaching, TestCommitCounts, TestFindWorktree, TestListEndpoint, TestRemoveEndpoint, TestWorktreeBindingSanitizer) and test/test_project_git_line.py (223 new lines) — neither file exists on origin/main; plus the 41-line addition to test_history_consolidation_retry.py, the +97 ProjectPicker.test.tsx tests, the new website/src/test/ChatSidebar.worktreeChip.test.tsx (118 lines), and the retargeting of test_worktree_create.py / test_dashboard_worktree_coverage.py onto git_exec.
  • Docs: the **Worktree sessions** paragraph appended to the Follow-up-suggestions line in docs/system-specs/modules/learn-cron-dashboard.md, and the two git-line bullets + the amended project context-group table row in docs/system-specs/modules/memory-skills-hooks.md. Neither is on main (git grep -F 'Worktree sessions' origin/main -- docs finds nothing of this shape).
  • The 4 PNGs under temp-screenshots/worktree-sessions/ — that directory does not exist on origin/main.
  • The generalization itself. main's only repo-agnostic worktree capability is feat(chat): agent-suggested follow-ups as a card above the composer #461's one-shot POST /api/worktree/create. All 21 other worktree commits in the landed-commit index for main are dev-fleet/pod scoped, and origin/main:src/kiro_crew/apps/builtins/dev_fleet/repository.py:286-287 still pins MAIN_REPO, MAIN_REPO_INFERRED = _default_main_repo_state() and BASE_BRANCH = "main" — exactly the two constants the PR body says it removes. git cherry patch-identity for recyclability exists on main only at repository.py:690-692, hardwired to {remote}/{BASE_BRANCH} and MAIN_REPO.

Suggested action: REBASE — the remainder is real work; rebase onto the landed part rather than closing.


From a repository-wide duplicate/overlap audit of every pull request open against main (2026-09-02, 330 PRs, one reviewer per PR). Each PR was read as its full merge-base diff plus its description and every comment and review, then compared against each candidate PR's own diff and against origin/main at 1a765b88ceb7. This PR is not being closed — the note is informational. If the reading is wrong, please correct the reasoning rather than just the conclusion.

@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion.

Relationship findings

  • PR #5890 is OVERLAPPING relative to this PR. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #5890: CONTINUE_DEVELOPMENT. Same file, complementary changes with no semantic collision; at most a trivial merge in one JSX attribute list. Files: website/src/components/ProjectPicker.tsx.
  • PR #7181 is OVERLAPPING relative to this PR. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7181: KEEP. Different user goals — parallel sessions on one repository via git worktrees versus a portable multi-repo context bundle — that touch the same slot-metadata seam. Both can ship; whichever lands second resolves a mechanical conflict in five files. Files: src/kiro_crew/dashboard/state.py, src/kiro_crew/history.py, src/kiro_crew/dashboard/chat_handlers.py, src/kiro_crew/dashboard/chat_persistence.py.
  • PR #7573 is OVERLAPPING relative to this PR. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7573: MERGE_DISCUSSION. Independent features that can both ship; the second to land must rebase the shared shelf-gate line and re-check the button-cap rationale. Files: website/src/components/ChatInput.tsx.
  • This PR is PARTIALLY_COVERED with PR #461. Coverage is explicitly incomplete; this finding is not a completion or closure claim. Recommended action for PR #3139: REBASE. Merged PR #461 supplies the git primitives and the allow-list matcher this PR relocates, and four later merged fixes have moved main's copy ahead of the PR's snapshot. The extraction should be redone from current main rather than replayed from the fork's older copy; everything that makes this PR a feature — the service, the two endpoints, the flag, the slot binding, the prompt git line, the whole frontend — is untouched by main. Files: src/kiro_crew/worktree/git_exec.py, src/kiro_crew/worktree/access.py, src/kiro_crew/dashboard/routes/chat.py.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

@nathanyi96

Copy link
Copy Markdown
Contributor Author

Closing this worktree feature PR as requested; it is unrelated to the Kanban App Store listing.

@nathanyi96 nathanyi96 closed this Sep 6, 2026
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) merge conflict Branch has merge conflicts with its base — author must resolve before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants