Skip to content

fix(kanban): stop new project tasks from starting on stale commits - #86611

Open
fangliquanflq wants to merge 10 commits into
NousResearch:mainfrom
fangliquanflq:fix/kanban-fetched-remote-worktree-base
Open

fix(kanban): stop new project tasks from starting on stale commits#86611
fangliquanflq wants to merge 10 commits into
NousResearch:mainfrom
fangliquanflq:fix/kanban-fetched-remote-worktree-base

Conversation

@fangliquanflq

Copy link
Copy Markdown
Contributor

What does this PR do?

This fixes new Kanban project tasks starting from a stale local checkout instead of the fetched remote default branch. Without the fix, newly dispatched work can begin before already-landed upstream changes and require manual branch recovery.

Symptom

When a project's persistent checkout is stale or locally diverged, a newly created task worktree inherits that checkout's HEAD even after origin/main has advanced.

Impact

New project task branches can omit upstream commits that were already present on the protected remote default branch. Existing task branches are not affected and continue to reuse their current tips.

Bug Cause

Trigger: hermes_cli/kanban_db.py:7680 / _ensure_git_worktree / the task branch does not exist yet.

Causal chain:

  1. The dispatcher materializes a new project task worktree from a persistent project checkout.
  2. The new-branch path runs git worktree add -b with the checkout's incidental local HEAD as its base without fetching or resolving the remote default.
  3. The task branch starts from a stale or divergent commit instead of the current protected remote tip.

Why it is wrong: The persistent checkout is only an anchor for creating linked worktrees; its local branch can legitimately lag or diverge and is not the source of truth for a new task branch.

Working sibling / contrast: The existing-branch path intentionally checks out the recorded task branch and must preserve its tip for retry idempotence. Local-only repositories have no remote source of truth and must retain the HEAD fallback.

Ruled out: Existing task-branch reuse is not the cause. Focused tests confirm that path keeps its prior tip even when the remote advances.

Fix

Fetch origin non-interactively before creating a new task branch, resolve a verified remote default ref with support for non-main and changed defaults, and use that ref as the worktree base. Fall back to local HEAD when fetch or remote-default resolution is unavailable, and leave the existing-branch path unchanged.

Related Issue

Closes #86574

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/kanban_db.py - fetch and resolve the remote default only for newly created task branches, with a safe local fallback.
  • tests/hermes_cli/test_kanban_worktree_isolation.py - cover stale and divergent main, non-main defaults, changed defaults, existing branches, and repositories without origin.

How to Test

  1. Create a project checkout whose local branch diverges from a newer origin/main.
  2. Materialize a new task worktree and verify its HEAD matches the freshly fetched remote default tip.
  3. Run the focused automated suites:
scripts/run_tests.sh tests/hermes_cli/test_kanban_worktree_isolation.py tests/hermes_cli/test_kanban_project_link.py tests/hermes_cli/test_kanban_board_project.py
uvx ruff check hermes_cli/kanban_db.py tests/hermes_cli/test_kanban_worktree_isolation.py
git diff --check hermes/main...HEAD

The focused test command completed with 14 passing tests on Windows 11.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run the repository test entry on the relevant tests and all tests pass
  • I've added tests for my changes
  • I've tested on Windows 11

Documentation & Housekeeping

  • Documentation updates are not applicable
  • Config example updates are not applicable
  • Architecture guide updates are not applicable
  • I've considered cross-platform impact; Git commands use argument lists and existing subprocess compatibility helpers
  • Tool description and schema updates are not applicable

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cron Cron scheduler and job management labels Aug 15, 2026
@enzo-adami

Copy link
Copy Markdown
Contributor

I reproduced one uncovered default-branch case against 449df1ee474c2ba9d1c09eaebf208bfa299351e3.

When the remote default is trunk and origin/main also exists, _worktree_base_ref() still returns origin/main because that ref is inserted first in candidates and the loop returns the first valid commit. The existing non-main test has no main branch, so it does not exercise the ordering.

Minimal reproduction result:

selected='refs/remotes/origin/main'
selected_oid=259f144555f8e49c3c30e015d1aefc90d61c6598
origin/main=259f144555f8e49c3c30e015d1aefc90d61c6598
origin/trunk=8ba53c761bb84f9a71124d6ebfad4a7dd71c9e78
wrong=True

The fixture was a bare origin with HEAD -> refs/heads/trunk, distinct commits on both main and trunk, then a fresh clone before calling _worktree_base_ref().

Suggested fix: resolve and verify refs/remotes/origin/HEAD (or the ls-remote --symref origin HEAD result) first, return that ref when valid, and use refs/remotes/origin/main only as the fallback when the remote default cannot be established. A regression should retain both remote branches while asserting the new task starts at origin/trunk.

@fangliquanflq

Copy link
Copy Markdown
Contributor Author

Fixed in 007d2dc by checking the verified remote default before falling back to origin/main. I also added a regression where distinct origin/trunk and origin/main both exist and confirmed the new worktree starts from trunk.

Verification:

  • scripts/run_tests.sh tests/hermes_cli/test_kanban_worktree_isolation.py — 8 passed
  • Real Git environment reproduction — the previous tip selected origin/main; the updated tip selects origin/trunk

@enzo-adami

Copy link
Copy Markdown
Contributor

Thanks — 007d2dc closes the fresh-clone main+trunk case. I found one remaining stale-clone counterexample: after cloning while main is default, change the bare remote HEAD to trunk but keep main alive. git fetch --prune leaves refs/remotes/origin/HEAD pointing to main, so the updated helper still selects the old main commit.

Reproduction on 007d2dc:

selected=refs/remotes/origin/main
local_origin_HEAD=refs/remotes/origin/main
remote_HEAD=refs/heads/trunk

Narrow draft against your exact branch: fangliquanflq#4

It resolves the remote HEAD after fetch and only falls back to the local symbolic ref if that lookup fails. Fresh validation: 9/9 isolation tests, 23 Kanban core tests + 1 skip, Ruff, Windows checker, and diff-check pass.

@fangliquanflq

Copy link
Copy Markdown
Contributor Author

Fixed in b57720d. The worktree-base resolver now queries the live remote HEAD first and explicitly refreshes that branch before selecting it, including repositories with restricted fetch refspecs or stale pre-existing tracking refs. The regression now keeps stale local origin/main and origin/trunk refs while verifying the new task starts from the latest remote trunk commit.\n\nVerification:\n- scripts/run_tests.sh tests/hermes_cli/test_kanban_worktree_isolation.py tests/hermes_cli/test_kanban_project_link.py tests/hermes_cli/test_kanban_board_project.py — 17 passed\n- uvx ruff check hermes_cli/kanban_db.py tests/hermes_cli/test_kanban_worktree_isolation.py — passed\n- git diff --check hermes/main...HEAD — passed\n- Real Git reproduction with a changed remote default, stale tracking refs, and a main-only fetch refspec — previous tip selected main; current tip selects the latest trunk commit

@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

fix(kanban): stop new project tasks from starting on stale commits

  1. hermes_cli/kanban_db.py _worktree_base_ref() — the fallback chain appends refs/remotes/origin/main and returns the first valid commit. When the remote default branch is not main and the earlier probes fail (server without symref support, or a stale local refs/remotes/origin/HEAD), the resolver silently starts new tasks from origin/main even though the project's actual default is trunk. Consider trusting a live ls-remote --symref origin HEAD result above any locally-cached ref, with origin/main only as a last resort — and log a warning when the fallback is taken.
  2. On total fetch failure the function returns "HEAD" (a local, possibly very stale commit) with only a debug-level log. Silently branching from local HEAD hides the failure from the dispatcher. Consider returning a failure indicator so the caller can surface it instead of proceeding on a stale base.
  3. The function issues up to three sequential git subprocesses (prune fetch, symref lookup, targeted fetch) per worktree creation. A single git fetch --prune origin plus rev-parse refs/remotes/origin/HEAD^{commit} after refresh would cover most cases in one round trip. Performance only; correctness unchanged.
  4. Test coverage is strong, but the "remote default changed on a stale clone" case — local refs/remotes/origin/HEAD pointing at a renamed/deleted branch while the fetch refspec excludes the new default — is exactly where the symbolic-ref fallback can select the old branch; an explicit regression for it would lock the resolver's behavior.

@fangliquanflq

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. I checked each point against the current branch:

  1. The live remote default is already resolved before cached refs or origin/main. I tightened this further: every selected branch is now force-fetched and commit-verified, and a failed fetch of a live default fails closed instead of falling back to a stale or different ref.
  2. Valid concern. Repositories with a configured origin now stop worktree creation when fetch fails or no freshly fetched default can be resolved. The local HEAD fallback remains only for repositories positively confirmed to have no origin.
  3. No performance-only rewrite was made. A single refspec-bound fetch cannot refresh a changed default outside the refspec or repair stale default tracking refs; the explicit resolution and targeted fetches preserve those correctness cases.
  4. This case was already covered by the changed-default regression with a main-only fetch refspec and stale local trunk tracking ref. I also added regressions for unreachable/configured origins, unresolved remote defaults, and failed targeted fetches.

Pushed through f224b3d19.

Verification:

  • scripts/run_tests.sh tests/hermes_cli/test_kanban_worktree_isolation.py tests/hermes_cli/test_kanban_project_link.py tests/hermes_cli/test_kanban_board_project.py — 21 passed
  • uvx ruff check hermes_cli/kanban_db.py tests/hermes_cli/test_kanban_worktree_isolation.py — passed
  • git diff --check — passed

@alt-glitch alt-glitch added needs-decision Awaiting maintainer decision before any implementation and removed needs-decision Awaiting maintainer decision before any implementation labels Aug 16, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #61626, #67760, and #68278 propose competing fresh-remote worktree-base policies for #86574. This PR now covers the reported non-main-default fallback; a maintainer should choose the contract.

@fangliquanflq

fangliquanflq commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for flagging the overlap. I reviewed the three referenced implementations against #86574 and the current branch:

The current regression suite covers stale/diverged main, non-main defaults with main present, changed and dangling defaults, restricted refspecs, configured-origin failures, local-only repositories, and existing-branch reuse. The PR is currently mergeable without conflicts and its required CI aggregate is passing.

So I agree this is now a maintainer contract-selection decision rather than a new code defect identified in this branch; no additional code change is needed for this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kanban project worktrees start from stale local HEAD instead of fetched origin/main

4 participants