{"record":{"id":"7463f2ee1bc42b27","repo":"gastownhall/beads","slug":"target-changed-files-changed","errorCode":null,"errorMessage":"target changed files changed","messagePattern":"target changed files changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1439,"sourceCode":"\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\n\tif !plan.force && currentTarget.status != \"\" {\n\t\tfacts.Cleanliness = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target is no longer clean\")}\n\t}\n\tif !os.SameFile(currentTarget.pathInfo, plan.target.pathInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.pathInfo, plan.target.pathInfo) {\n\t\tfacts.TargetDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target directory identity changed\")}\n\t}\n\tfacts.TargetDirectory = worktreeremove.InvariantStable\n\tif !os.SameFile(currentTarget.gitDirInfo, plan.target.gitDirInfo) ||\n\t\t!samePinnedFileMetadata(currentTarget.gitDirInfo, plan.target.gitDirInfo) {\n\t\tfacts.GitAdminDirectory = worktreeremove.InvariantChanged\n\t\treturn worktreeRevalidationObservation{facts: facts, err: fmt.Errorf(\"target git directory identity changed\")}\n\t}\n\tfacts.GitAdminDirectory = worktreeremove.InvariantStable","sourceCodeStart":1421,"sourceCodeEnd":1457,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1421-L1457","documentation":"bd throws this error during worktree revalidation when the fingerprint of the target's changed files (statusFingerprint) differs from the planned snapshot, even though overall cleanliness may look the same. It means the specific set of dirty files changed since planning, so the plan's assumptions about the worktree contents are stale and the operation aborts.","triggerScenarios":"Executing a planned worktree operation after the set of modified/untracked files changed — e.g. a new file was edited, one dirty file was reverted while another appeared, or untracked files were added/removed — such that `git status --porcelain` content (fingerprinted) differs from plan time.","commonSituations":"A build/codegen step regenerated files between plan and execution; the user edited different files while a bd operation was pending; a linter/formatter or IDE auto-fixed files; concurrent agent sessions touched different files in the same worktree.","solutions":["Re-run the bd command to rebuild the plan from the current status fingerprint","Commit, stash, or clean the changed files so the worktree matches the planned fingerprint","Use the force flag if proceeding with the changed file set is safe","Find and disable the concurrent writer (formatter, build, IDE autosave) or sequence operations to avoid overlap"],"exampleFix":"// before: fingerprint drifts while operation is queued\nplan := snapshot(target) // fingerprint F1\nautofixer.Run(wt) // fingerprint becomes F2\nexecute(plan) // error: target changed files changed\n// after: execute immediately after snapshot, or re-snapshot\nplan := snapshot(target)\nexecute(plan) // no intervening writers","handlingStrategy":"validation","validationCode":"out, _ := exec.Command(\"git\", \"-C\", wt, \"status\", \"--porcelain\").Output()\nif fingerprint(string(out)) != plan.Target.StatusFingerprint {\n    // dirty-file set changed; rebuild the plan before invoking bd\n}","typeGuard":"func dirtySetUnchanged(plannedFingerprint string, wt string) bool {\n    out, err := exec.Command(\"git\", \"-C\", wt, \"status\", \"--porcelain\").Output()\n    return err == nil && fingerprint(string(out)) == plannedFingerprint\n}","tryCatchPattern":"err := bdRemoveWorktree(plan)\nif err != nil && strings.Contains(err.Error(), \"target changed files changed\") {\n    plan = rebuildPlan(plan.Target)\n    err = bdRemoveWorktree(plan)\n}","preventionTips":["Don't edit, generate, or format files in the worktree between planning and execution","Add generated artifacts to .gitignore or direct build output outside the worktree","Execute the operation right after snapshotting to shrink the race window","Compare `git status --porcelain` output before invoking to detect drift early"],"tags":["git","worktree","dirty-files","fingerprint","stale-state"],"backgroundTag":"git-worktree-dirty-state-changed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}