{"record":{"id":"3a71fce0b6b72cad","repo":"gastownhall/beads","slug":"target-head-changed-from-s-to-s","errorCode":null,"errorMessage":"target HEAD changed from %s to %s","messagePattern":"target HEAD changed from (.+?) to (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1423,"sourceCode":"\tfacts.TargetPath = worktreeremove.InvariantStable\n\n\tcurrentTarget, err := inspectWorktreeTarget(ctx, plan.git, currentEntry)\n\tif err != nil {\n\t\treturn worktreeRevalidationObservation{facts: facts, err: err}\n\t}\n\tif !sameWorktreePath(currentTarget.gitDir, plan.target.gitDir) {\n\t\tfacts.GitAdminDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git directory changed\")}\n\t}\n\tfacts.GitAdminDirectory = worktreeremove.InvariantStable\n\tif !sameWorktreePath(currentTarget.commonDir, plan.commonDir) {\n\t\tfacts.CommonDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target common git directory changed\")}\n\t}\n\tfacts.CommonDirectory = worktreeremove.InvariantStable\n\tif currentTarget.headOID != plan.target.headOID {\n\t\tfacts.Head = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target HEAD changed from %s to %s\", plan.target.headOID, currentTarget.headOID)}\n\t}\n\tfacts.Head = worktreeremove.InvariantStable\n\tif currentTarget.status != plan.target.status {\n\t\tif (currentTarget.status == \"\") != (plan.target.status == \"\") {\n\t\t\tfacts.Cleanliness = worktreeremove.InvariantChanged\n\t\t} else {\n\t\t\tfacts.Cleanliness = worktreeremove.InvariantStable\n\t\t}\n\t\tfacts.StatusBytes = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target cleanliness changed\")}\n\t}\n\tfacts.Cleanliness = worktreeremove.InvariantStable\n\tfacts.StatusBytes = worktreeremove.InvariantStable\n\tif currentTarget.statusFingerprint != plan.target.statusFingerprint {\n\t\tfacts.DirtyFileFingerprint = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target changed files changed\")}\n\t}\n\tfacts.DirtyFileFingerprint = worktreeremove.InvariantStable","sourceCodeStart":1405,"sourceCodeEnd":1441,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1405-L1441","documentation":"This error comes from the worktree removal/revalidation flow in bd. Before executing a planned worktree operation, bd re-validates that the target worktree is in the exact state observed when the plan was built; if the target's HEAD OID differs from the planned HEAD OID, the plan is considered stale and the operation aborts with this error. It is a deliberate safety guard against destroying or modifying a worktree that has moved to a different commit since planning.","triggerScenarios":"Running a bd worktree remove (or similar planned mutation) when `git -C <worktree> rev-parse HEAD` at execution time returns a different commit than at plan time — e.g. a checkout, pull, rebase, reset, or commit landed on the target worktree between planning and execution, or by a concurrent process.","commonSituations":"A background fetch/auto-update or IDE checked out another branch in the worktree after bd captured its state; the user ran git commands in the worktree while a bd command was queued or scripted; a CI/script re-planned long after snapshotting the worktree; another agent session committed to the worktree concurrently.","solutions":["Re-run the bd command so a fresh plan is built against the current HEAD","Verify the worktree's expected commit with `git -C <worktree> rev-parse HEAD` and restore the planned commit (git checkout/reset) if the change was unintentional","If the HEAD change is intentional and safe, use the force option (plan.force) so the operation proceeds despite the drift","Check for concurrent processes (other bd sessions, IDEs, cron git jobs) touching the worktree and serialize operations"],"exampleFix":"// before: plan built earlier, then executed later against a moved HEAD\nobs := revalidate(plan) // errors: target HEAD changed from abc123 to def456\n// after: re-plan immediately before executing\nplan := buildWorktreeRemovePlan(target) // fresh snapshot\nobs := revalidate(plan) // passes because HEAD is sampled just before execution","handlingStrategy":"validation","validationCode":"currentHEAD, _ := exec.Command(\"git\", \"-C\", wt, \"rev-parse\", \"HEAD\").Output()\nif strings.TrimSpace(string(currentHEAD)) != plannedHeadOID {\n    // re-plan or abort before invoking the bd operation\n}","typeGuard":"func headUnchanged(planned string, wt string) bool {\n    out, err := exec.Command(\"git\", \"-C\", wt, \"rev-parse\", \"HEAD\").Output()\n    return err == nil && strings.TrimSpace(string(out)) == planned\n}","tryCatchPattern":"err := bdRemoveWorktree(plan)\nif err != nil && strings.Contains(err.Error(), \"target HEAD changed\") {\n    plan = rebuildPlan(plan.Target) // re-snapshot and retry once\n    err = bdRemoveWorktree(plan)\n}","preventionTips":["Snapshot the worktree state immediately before executing, not long in advance","Avoid running git checkout/pull/rebase in the worktree while a bd operation is pending","Serialize bd operations with other automation touching the same worktree (locks or a job queue)","If HEAD movement is expected, use the force option explicitly rather than fighting the guard"],"tags":["git","worktree","stale-state","race-condition","safety-check"],"backgroundTag":"git-worktree-head-changed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}