Skip to content

chore: declare the generated route tree as reclaimable build output - #33

Open
cristim wants to merge 1 commit into
Infrawrench:mainfrom
cristim:chore/declare-reclaim-build-outputs
Open

cristim wants to merge 1 commit into
Infrawrench:mainfrom
cristim:chore/declare-reclaim-build-outputs

Conversation

@cristim

@cristim cristim commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Adds .reclaim-build-outputs, listing the one generated file that keeps merged worktrees of this repo from being cleaned up: apps/web/src/routeTree.gen.ts.

Why

The worktree reclaim script in LeanerCloud/dotclaude (scripts/reclaim-merged-worktrees.sh) removes a merged PR's worktree only when it holds no unsaved work. It treats any ignored file outside its build allowlist (node_modules, dist, build and similar) as possible unsaved work. routeTree.gen.ts is written by the TanStack Router Vite plugin (apps/web/vite.config.ts) on every web build or dev run, and is already gitignored (.gitignore line 7). It is not on that allowlist, though, so every merged worktree that ran the web app is kept and needs a manual decision.

Since dotclaude#178, a repo can list exact generated paths in .reclaim-build-outputs:

  • The file is read only from the repo's default branch, never from a worktree or a fork, so only whoever can merge here decides what may be deleted.
  • Entries are exact paths: no globs and no ./.. components.
  • A listed file is allowed only when git reports it as its own ignored entry. Anything else ignored still blocks.

Effect

  • Five merged worktrees currently blocked on exactly this file become reclaimable. Their only other ignored entry is the .claude/skills/pnpm-* symlink into node_modules, which is already allowed (dotclaude#176).
  • Nothing changes for builds, CI or the app: the file is read only by that script.

Verification

  • An independent reviewer built scratch fixtures with the script from dotclaude main, this exact file on the fixture's default branch, and nibblets' real .gitignore. Results:
    • A worktree holding only routeTree.gen.ts, with the dangling pnpm skill symlink beside it, is reclaimed.
    • Without the declaration, it is blocked.
    • With an extra ignored file, it is blocked.
    • With the file inside an ignored directory instead, it is blocked.
  • The parser accepts the file: LF line endings, a final newline, and comment lines skipped.

🤖 Generated with Claude Code

Merged worktrees stay on disk because the worktree reclaim script
treats any ignored file outside its build allowlist as possible
unsaved work, and apps/web/src/routeTree.gen.ts (written by the
TanStack Router Vite plugin on every build or dev run, and already
gitignored) is not on that allowlist.

The script reads exact paths from .reclaim-build-outputs on the
default branch only, so listing the file here lets it remove those
worktrees without asking. It still blocks on any other ignored file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cristim

cristim commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Independent review 1 (claude-fleet, cold): APPROVE dc491a8

Findings: nothing needs fixing. One observation: another local worktree (no PR) also holds the ignored directories apps/desktop/out/ and apps/web/dist-central/. The declaration format covers only exact files, so this change could not cover those, and that worktree is not a merged-PR worktree anyway.

Attacked:

  • Parsing:
    • The file has LF line endings, no trailing whitespace and a final newline.
    • All three comment lines start with # and are skipped by load_declared_outputs.
    • The path uses only [A-Za-z0-9._@+/-], has no ., .. or .git component and no glob, so decl_valid accepts it.
  • Claims:
    • .gitignore line 7 on main is apps/web/src/routeTree.gen.ts.
    • apps/web/vite.config.ts loads tanstackRouter(...) from @tanstack/router-plugin/vite.
  • Completeness: every local merged-PR worktree of this repo was inspected read-only. Each holds exactly routeTree.gen.ts plus the .claude/skills/pnpm-* symlink into node_modules, which dotclaude already allows.
  • Commit text: no personal paths, usernames, hostnames or emails.

Verified locally: scratch fixtures using dotclaude main's script and test helpers, nibblets' real .gitignore, and this exact blob on the fixture's default branch. All six cases behave as expected:

  • n1: declared, routeTree.gen.ts in a tracked src dir → reclaimed.
  • n2: not declared → blocked ("not on the build allowlist").
  • n3: declared plus an extra ignored debug.log → blocked on the extra file.
  • n4: declared, but git reports the whole apps/ dir as ignored → blocked.
  • n5: declared, but the file sits inside an ignored directory → blocked.
  • n6: declared, plus the dangling pnpm skill symlink and the tracked skills .gitignore (the real worktrees' shape) → reclaimed.

The reclaim script was not run against any real clone or worktree.

@cristim

cristim commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Independent review 2 (claude-fleet, cold): APPROVE dc491a8

This reviewer had no access to review 1's reasoning and built its own fixture.

Findings: none blocking. Two candidates were checked and dropped:

  • pnpm typecheck fails without a prior web build, because main.tsx imports ./routeTree.gen.ts. That is already true of every fresh clone, and CI builds web before typecheck (ci.yml:41-43). Deleting the file together with a worktree that is itself being removed loses nothing.
  • .dockerignore does not list the new file. That does not matter: the Dockerfiles copy only packages, apps/web and apps/server, never root dotfiles.

Attacked:

  • Could it delete real work?
    • git log --all -- apps/web/src/routeTree.gen.ts is empty, so the file was never committed on any branch.
    • It is gitignored (.gitignore:7) and prettier-ignored (.prettierignore:3).
    • The tanstackRouter() plugin regenerates it on every dev run and build. Every build path regenerates it before use: web build, the Docker web stage (server.Dockerfile:11) and CI (ci.yml:42). The desktop build does not use it.
  • Trust model:
    • Declarations are fetched from origin's default branch with ls-tree/cat-file, never read from the worktree.
    • The default branch comes from gh repo view on the repo parsed from remote.origin.url.
    • is_declared needs an exact grep -Fx match and refuses directories and symlinks.
    • A fixture with the declaration only on a fork's main stayed blocked.
  • PR body claims:
    • "Five merged worktrees" holds: six local worktrees have only the pnpm skill symlink plus this file as ignored entries, and five of them are merged with the tip at or behind the PR head. The sixth is still open.
    • "Nothing changes for builds, CI or the app" holds: nothing in the repo references the new file.
  • Tooling: prettier --check . with the repo's config passes. The file is ASCII with LF endings and a final newline.

Verified locally: an independent fixture using dotclaude main's script, bare origin and fork repos, nibblets' real .gitignore, this exact blob, and a fake gh returning MERGED at the tip, run with --apply:

  • Declaration on origin: reclaimed.
  • No declaration: blocked on apps/web/src/routeTree.gen.ts.
  • Declaration only on the fork: blocked.
  • Extra ignored files: blocked.

CI on dc491a8: "Typecheck, lint and test" and "Docker images" both pass. Read-only checks only were run on the real clone, never the reclaim script.

Gate: two independent reviews APPROVE at this head, and CI is green. This is ready for a maintainer merge.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant