Skip to content

Worktree setup writes .bot/workspace/tasks/ into the main repo's .git/info/exclude #681

Description

@EnmaJim

What happened

Ensure-DotbotWorktreeExcludes (Dotbot.Worktree.psm1:538-579) is called once, from :800, with a
task worktree path. It resolves its target file like this:

$excludePath = git -C $WorktreePath rev-parse --git-path info/exclude

For a linked worktree, --git-path info/exclude resolves into the shared common git directory
i.e. the main repository's .git/info/exclude, not anything worktree-local. It then writes a
marker-delimited block that includes the project's task tree:

# dotbot generated execution environment: start
.mcp.json
.claude/
.codex/
.opencode/
.agents/
.gemini/
.bot/.control
.bot/.control/
.bot/.handoffs
.bot/.handoffs/
.bot/workspace/tasks          <-- now ignored in the MAIN repo
.bot/workspace/tasks/
.bot/content/
.bot/hooks/
.bot/settings/
# dotbot generated execution environment: end

Confirmed on a test project — .git/info/exclude mtime matches the first worktree creation, and:

> git check-ignore -v .bot/workspace/tasks/workflow-runs/2026-08-05-smoke-test-a09g/run.json
.git/info/exclude:19:.bot/workspace/tasks/   .bot/workspace/tasks/workflow-runs/.../run.json

Three consequences follow.

(1) It contradicts the documented contract. README.md:118 states verbatim:

Keep .bot/ tracked in git. The workspace tree (tasks/decisions/plans/product) is your team's
audit trail; the project's .gitignore already covers the gitignored bits. Worktree state replays
depend on .bot/workspace/ being visible to git.

README.md:105 and :216 both annotate workspace/ as (tracked).

(2) git add on that tree becomes a silent no-op, which breaks Dotbot's own commit step.
Complete-TaskWorktree:1880 stages task state with plain git add and no -f:

git add .bot/workspace/tasks/ .bot/workspace/decisions/

Demonstrated:

> git add .bot/workspace/tasks/
The following paths are ignored by one of your .gitignore files:
.bot/workspace/tasks
hint: Use -f if you really want to add them.
> git diff --cached --name-only
(empty)                                   <- nothing staged

> git add -f .bot/workspace/tasks/standalone/probe-task.json
> git diff --cached --name-only
.bot/workspace/tasks/standalone/probe-task.json    <- only -f works

(3) State accumulates invisibly and survives cleanup. After four workflow runs,
git status --porcelain was empty while four run directories sat on disk. Only --ignored
revealed them, and git clean -fd cannot remove them (ignored paths need -x):

> git status --porcelain
(empty)
> git status --porcelain --ignored -- .bot/workspace/tasks
!! .bot/workspace/tasks/workflow-runs/2026-08-05-smoke-test-BMrY/
!! .bot/workspace/tasks/workflow-runs/2026-08-05-smoke-test-Ikka/
!! .bot/workspace/tasks/workflow-runs/2026-08-05-smoke-test-MhFg/
!! .bot/workspace/tasks/workflow-runs/2026-08-05-smoke-test-a09g/

Dotbot's own integrity module warns about exactly this failure mode —
src\mcp\modules\FrameworkIntegrity.psm1:15-18:

If .bot/ is gitignored (repo .gitignore, core.excludesFile, info/exclude, or a deep rule),
every git-based check returns empty and silently passes.

Finally, .git/info/exclude is never committed, so this is invisible in code review, differs per
machine, and dotbot doctor does not mention it — doctor reported PASS: 8 WARN: 0 FAIL: 0 in
exactly this state.

What you expected

Creating a task worktree should not change ignore rules for the operator's main checkout, and
.bot/workspace/tasks/ should stay tracked as documented.

If generated files must be hidden inside the worktree, write to a worktree-local exclude
(.git/worktrees/<name>/info/exclude) rather than the shared common dir, or scope the entries to the
paths that are genuinely generated (.mcp.json, .claude/, .bot/.control, .bot/.handoffs) and
leave .bot/workspace/tasks/ alone. If the tree is genuinely meant to be untracked, the README and
the git add at :1880 need to change to match.

Steps to reproduce

$env:DOTBOT_HOME = '<dotbot checkout>'
$repo = 'C:\tmp\exclude-repro'
git init -b master $repo; cd $repo
git config user.email a@b.c; git config user.name t
'x' > README.md; git add -A; git commit -qm init
& $env:DOTBOT_HOME\bin\dotbot.ps1 init -y
git add -A; git commit -qm 'chore: dotbot init'

# baseline: the task tree is tracked and not ignored
git check-ignore -v .bot/workspace/tasks     # -> no output
type .git\info\exclude                       # -> stock git comments only

# run any workflow so a task worktree gets created (smoke-test needs no AI provider)
& $env:DOTBOT_HOME\bin\dotbot.ps1 workflow add smoke-test -y
& $env:DOTBOT_HOME\bin\dotbot.ps1 run smoke-test --watch

# after: the main repo's exclude file has been rewritten
type .git\info\exclude                       # -> contains .bot/workspace/tasks/
git check-ignore -v .bot/workspace/tasks     # -> .git/info/exclude:NN:.bot/workspace/tasks/
git add .bot/workspace/tasks/                # -> "paths are ignored ... Use -f"
git status --porcelain                       # -> clean, even though run state exists on disk
git status --porcelain --ignored -- .bot/workspace/tasks   # -> !! the run directories

Environment

OS: Windows 11 Pro 10.0.26200 | dotbot v4.0.2 (main @ 7c95b466)
pwsh 7.6.4 | git 2.54.0.windows.1
DOTBOT_HOME set explicitly; %APPDATA%\dotbot\user-settings.json absent
Target repo: 1129 files, .bot committed, single remote, master default
dotbot doctor in the affected state: PASS 8 / WARN 0 / FAIL 0

Severity

high

Logs / screenshots

.git/info/exclude mtime lines up with the third run's worktree creation, not with dotbot init:

.git\info\exclude  LastWriteTime  2026-08-05T11:53:23   (run 3 started 11:53:16)
dotbot init ran at 11:37

The single caller, which passes a worktree path — so the write to the main repo's file is incidental
rather than intended:

# Dotbot.Worktree.psm1:799-801
$frameworkRoot = Get-DotbotWorktreeFrameworkRoot
Ensure-DotbotWorktreeExcludes -WorktreePath $WorktreePath
Remove-DotbotGeneratedPath -Path (Join-Path $WorktreePath '.gemini') -WorktreePath $WorktreePath

Manual remediation, per project, on every machine: delete the marker-delimited block from
.git/info/exclude, then git add -f .bot/workspace/tasks/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Inbox

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions