Files
kandev/docs/plans/worktree-refresh-fail-closed/plan.md
T

187 lines
8.6 KiB
Markdown

---
created: 2026-08-25
status: implemented
requirements:
- REQ-WORKSPACES-WORKTREE-BASE-REFRESH-001
system_design:
- ../../specs/workspaces/system-design/worktree-base-refresh.md
legacy_specs: []
---
# Implementation Plan: Fail-Closed Worktree Refresh
## Overview
Make a required worktree refresh stop task launch when remote state cannot be
verified. The implementation first routes each repository through the correct
credential boundary. It then makes worktree fetch and base-ref selection
fallible. The final work verifies durable launch errors on desktop and mobile
and updates the public Git guidance.
## Confirmed root cause
- `internal/worktree.Manager.pullBaseBranch` logs fetch failure and returns the
requested local ref through `handleFetchFallback`.
- `pullCurrentBranchOrFallback` also converts pull failure into a successful
local or remote ref. It does not reject diverged refs.
- `internal/repoclone.Cloner` treats fetch failure as nonfatal when it finds an
existing cached checkout through an `Ensure*` path.
- `executor.resolveTaskRepoInfoForSession` runs an exact-scope strict refresh
only for plugin-managed repositories. Other managed providers can reach the
worktree manager without a successful authenticated refresh.
- The repository setting is presented as “Always pull before creating a new
worktree.” Continuing from a stale ref violates this user contract.
- Existing fallback tests confirm that the current behavior is intentional,
not an isolated command error.
## Scope
### In scope
- Treat pull-before-worktree as a required remote-refresh gate for new and
recreated worktrees.
- Select a strict refresh route that preserves managed-provider credentials and
executor-inherited Git transport.
- Stop preparation on fetch failure, missing fetched base, divergent refs, or
uncertain ancestry.
- Preserve offline behavior when pull-before-worktree is disabled.
- Preserve a local base that contains the fetched remote base.
- Stop multi-repository launch before agent startup when one required refresh
fails.
- Reconcile a GitHub pull request's current base at launch and during polling.
- Recover a proven deleted remote base through a separately refreshed default
base while retaining fail-closed behavior for all other fetch failures.
- Project a credential-safe, repository-specific launch error through the
existing task error surface.
- Document SSH setup and the fail-closed behavior in the public Git guidance.
### Out of scope
- Making setup scripts fatal.
- Changing agent Git transport after launch.
- Refreshing an existing worktree that Kandev reuses without recreation.
- Resetting, merging, rebasing, or deleting user refs.
- Adding a new recovery action or user-interface layout.
- Adding a background refresh worker.
## Technical approach
The executor will resolve the task Git credential policy before lifecycle
preparation. A backend-managed provider checkout will pass a deferred strict
`repoclone` refresh callback with the same provider-specific credentials as
clone. The callback runs only when a worktree is materialized or recreated; a
valid reusable worktree bypasses it. An executor-inherited checkout will retain
its reconciled origin and enter the strict worktree-manager fetch path. Only a
completed refresh can set `RemoteSyncHandled`.
The worktree manager will return `(ref, error)` from required sync and
base-selection functions. Fetch failure will propagate instead of returning a
fallback ref. After a successful fetch, ancestry checks will select a ref only
when one ref contains the other. Divergence and an uncertain check will return
an error without changing either branch.
Lifecycle and executor code will retain repository identity while they wrap
the error. Existing task launch-failure persistence will project the failure.
No database or WebSocket schema change is planned.
## Work orders
- [completed] [Task 01: Route Required Repository Refresh](task-01-route-required-refresh.md)
- [completed] [Task 02: Reject Stale Worktree Fallbacks](task-02-reject-stale-fallbacks.md)
- [completed] [Task 03: Project Refresh Launch Failures](task-03-project-refresh-launch-failures.md)
- [completed] [Task 04: Document Required Repository Refresh](task-04-document-required-refresh.md)
- [completed] [Task 05: Support Stacked PR Base Retargeting](task-05-support-stacked-pr-base-retargeting.md)
## Dependency order
```text
Task 01 -> Task 02 -> Task 03 -> Task 04 -> Task 05
```
The package is sequential. Task 02 consumes the refresh-route contract from
Task 01. Task 03 verifies the combined launch boundary. Task 04 documents the
verified behavior.
## Verification strategy
- Unit tests prove credential-route selection and exact-scope managed refresh.
- Worktree integration tests prove fetch failure, comparable refs, divergence,
timeout, and the disabled-policy path.
- Orchestrator tests prove repository-specific failure and no agent startup for
multi-repository tasks.
- Existing launch-failure E2E tests prove the durable error on desktop and
mobile after reload.
- Public documentation validators prove links, commands, and headings.
- Backend race tests and lint cover the combined affected packages.
## Verification results
- Worktree, lifecycle, orchestrator, and executor race suites passed, including
6,049 tests in the combined affected-package run.
- Backend build, E2E plugin packaging, and production E2E build passed.
- The launch-failure recovery spec passed all 3 Chromium tests and both
`mobile-chrome` tests.
- Backend lint, public-doc validators, specification lint, and `git diff
--check` passed.
### Review remediation results
- Rebuilt the PR branch from the focused worktree-refresh, launch-failure, and
required fixture-origin commits; unrelated automation and broad E2E timing
changes are no longer part of the branch.
- Added Create and recreate regressions for normal managed branches and
`origin/pr/<N>` refs with a pre-existing local branch behind the refreshed
source. The refreshed source is now passed to `git worktree add -B` after
ancestry selection.
- Reverified 2,972 affected backend race tests, zero backend lint issues, 3/3
desktop launch-failure tests, and 2/2 mobile launch-failure tests with strict
no-retry E2E execution.
### PR fixup remediation results
- Reproduced the six failed E2E shard causes at the focused branch head: local
origin fixture collisions, ambiguous folder and branch locators, missing
offline refresh opt-outs, shared Git history, and a GitLab setup that needed
to remain offline.
- Kept the remediation scoped to the refresh contract: exact folder/branch
locators, explicit offline options for local fixtures, and a disposable
repository for the PR-deduplication history assertion. No generic timing
changes were restored.
- With retries disabled, the affected desktop command passed 21/21 tests, the
affected mobile command passed 2/2 tests, and the complete Git Changes Panel
file passed 21/21 tests. Prettier and `git diff --check` also passed.
### Stacked PR base retargeting results
- Added launch-time live GitHub base resolution and polling-time persistence
for PR-backed task repositories, with best-effort failure handling.
- Added a verified default-base fallback only for missing remote base refs;
authentication, transport, timeout, and other refresh failures remain fatal.
- The affected backend package tests, complete backend test suite, backend
lint, backend build, specification validators, and public-documentation
validators passed.
## Risks
- A managed refresh that ignores task Git policy can replace an
executor-inherited SSH origin with HTTPS. Route selection needs explicit
policy tests.
- A naive remote-first rule can hide local-only commits. The ancestry table in
the system design is the required selection rule.
- Existing tests currently require stale fallback behavior. Replace those
assertions with fail-closed tests rather than removing coverage.
- `executor_resume.go` is active code with broad resume coverage. Keep the
route helper narrow and run fresh-launch and resume suites together.
- Error wrapping can leak raw Git output. Use existing redaction and bounded
launch-error helpers.
- Live GitHub base resolution is best-effort. The polling projection and
refreshed default fallback must independently cover unavailable lookups.
- A multi-repository preparation attempt can refresh earlier repositories
before a later one fails. This is safe on-disk cache progress, but agent
startup must remain atomic.
## Package handoff
Implementation followed the sequential TDD work orders. Each work order and
the plan status are updated after its verification commands passed.