fix(runtime): ignore Ultrafuzz's own worktree roots so successful runs reap task worktrees (#1227) - #1259
fix(runtime): ignore Ultrafuzz's own worktree roots so successful runs reap task worktrees (#1227)#1259mrthankyou wants to merge 2 commits into
Conversation
…s reap task worktrees (#1227) Smithers reaps a finished run's worktree only when `git status --porcelain` is empty. Task preparation writes `.ultrafuzz/` and `artifacts/` into every worktree, so with keep_workspaces = false a successful run still kept every task worktree as unsaved work. Preparation now gives each of those roots a `.gitignore` of `*`. An existing `.gitignore` there is left alone, a root that is not a real directory is skipped, and a failure only leaves the worktree kept, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| const directory = path.join(workspaceRoot, root); | ||
| fs.mkdirSync(directory, { recursive: true }); | ||
| if (!fs.lstatSync(directory).isDirectory()) continue; | ||
| fs.writeFileSync(path.join(directory, ".gitignore"), "*\n", { flag: "wx" }); |
There was a problem hiding this comment.
Selective ignores prevent cleanup
If a target already tracks an artifacts/.gitignore or .ultrafuzz/.gitignore that does not cover every file Ultrafuzz creates there, the wx write leaves that file unchanged and adds no new ignore rule. Those generated files remain untracked, so Git status is not clean and a successful run still cannot remove the worktree when keep_workspaces = false. The new test includes a selective artifacts/.gitignore, but does not check whether its worktree becomes clean.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/workspace-runtime-roots.ts
Line: 28
Comment:
**Selective ignores prevent cleanup**
If a target already tracks an `artifacts/.gitignore` or `.ultrafuzz/.gitignore` that does not cover every file Ultrafuzz creates there, the `wx` write leaves that file unchanged and adds no new ignore rule. Those generated files remain untracked, so Git status is not clean and a successful run still cannot remove the worktree when `keep_workspaces = false`. The new test includes a selective `artifacts/.gitignore`, but does not check whether its worktree becomes clean.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…ts/ (#1227) The `*` rule also hid files an agent leaves beside its declared outputs, which verification does not publish, so reaping a successful run's worktree would have deleted them. artifacts/.gitignore now names only the task's declared outputs, so any other file keeps the worktree as before. Tests also show that a target's own .gitignore there leaves the worktree kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| ): void { | ||
| const declared = LITERAL_IGNORE_PATH.test(task.attemptId) | ||
| ? task.outputPaths | ||
| .filter((outputPath) => LITERAL_IGNORE_PATH.test(outputPath)) |
There was a problem hiding this comment.
Valid output paths remain visible
Declared output paths may contain @ or +, but this filter leaves those paths out of artifacts/.gitignore. When a successful task writes such an output to its worktree mirror, Git still reports it as untracked, so the worktree is kept despite keep_workspaces = false.
Knowledge Base Used: Runtime orchestration
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/workspace-runtime-roots.ts
Line: 31
Comment:
**Valid output paths remain visible**
Declared output paths may contain `@` or `+`, but this filter leaves those paths out of `artifacts/.gitignore`. When a successful task writes such an output to its worktree mirror, Git still reports it as untracked, so the worktree is kept despite `keep_workspaces = false`.
**Knowledge Base Used:** [Runtime orchestration](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/runtime-orchestration.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| .map((outputPath) => `/${task.attemptId}/${outputPath}\n`) | ||
| : []; | ||
| writeRootIgnoreFile(workspaceRoot, ".ultrafuzz", "*\n"); | ||
| writeRootIgnoreFile(workspaceRoot, "artifacts", ["/.gitignore\n", ...declared].join("")); |
There was a problem hiding this comment.
Runtime companions prevent cleanup
The runtime writes verified companion files into the worktree mirror that are not listed in task.outputs. For example, a successful invariant-suite task writes invariant-suite-manifest.json and suite files there. Because this ignore file covers only declared outputs, Git reports those runtime-written files as untracked and keeps the worktree instead of reaping it.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/workspace-runtime-roots.ts
Line: 35
Comment:
**Runtime companions prevent cleanup**
The runtime writes verified companion files into the worktree mirror that are not listed in `task.outputs`. For example, a successful invariant-suite task writes `invariant-suite-manifest.json` and suite files there. Because this ignore file covers only declared outputs, Git reports those runtime-written files as untracked and keeps the worktree instead of reaping it.
**Knowledge Base Used:**
- [Execution runtime](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/execution-runtime.md)
- [Runtime orchestration](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/runtime-orchestration.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
Thanks for digging into #1227. We're not merging this yet. Two concerns, both from reading the code. I haven't reproduced either end to end. 1. Workspace-patch producers get reaped, and the post-run gate then can't read them. Declared outputs are ignored from task preparation onward. A 2. A rejected output loses its only copy. The ignore entries are written before verification runs. So a Smaller:
More broadly, we've seen many failed runs tied to workspaces and declared outputs. We think this area needs a bigger simplification, not another layer on top of the current lifecycle. We'd like to step back and look at the workspace / declared-output lifecycle as a whole before changing when worktrees are reaped. Leaving this open for now. |
Refs #1227. This covers the worktree-reaping part. The rest is split out into #1257 (
cleanleaves worktree registrations, branches and the engine run record) and #1258 (runshould reject a run ID the engine already knows before building the snapshot).Problem
With
keep_workspaces = false, a successful run keeps every task worktree. The engine (@smthrs/engine0.35.0,reapFinishedRunWorktreesandgitWorktreeHasUnsavedWork) reaps a finished run's worktree only whengit status --porcelainis empty. Task preparation writes.ultrafuzz/(schemas, validator, prompt authority) and theartifacts/mirror into every worktree. Ultrafuzz leaves those out of its own git commands by pathspec (WORKSPACE_RUNTIME_ROOTS), but nothing tells git to ignore them. So every worktree showed?? .ultrafuzz/and?? artifacts/, and the engine loggedkeeping worktree with unsaved workonce per task.Still present on
unstable(5c1a9775), confirmed by code reading. No commit since the issue was filed touches this.Fix
ignoreWorkspaceRuntimeRoots(packages/runtime/src/workspace-runtime-roots.ts) runs inprepareArtifactMirrorjust before the schema bundle is written..ultrafuzz/holds only Ultrafuzz's files, so it gets a.gitignoreof*, which also ignores itself.artifacts/gets a.gitignorenaming only the task's declared outputs (/<attempt>/<output path>) and itself. Any other file an agent leaves there stays untracked and keeps the worktree. Verification publishes only declared outputs, so ignoring everything there would delete such a file when the worktree is removed (Greptile). An output path that isn't a literal pattern is not listed.info/exclude: a linked worktree shares it with the project's own checkout..gitignorein either directory, which the target may track, is left alone. That worktree is still kept, because editing a tracked file would itself count as unsaved work. A root that isn't a real directory is skipped, and any failure only leaves the worktree kept, as today.docs/reference/configuration.mdand a CHANGELOG entry.Tests
workspace-runtime-roots.test.tsuses a real repository with a linked worktree:.ultrafuzz/files and declared outputs leavegit status --porcelainempty, the same check the engine applies;.ultrafuzz/file;artifacts/<attempt>/debug.log, or atest/foundry/file, keeps the worktree;artifacts/.gitignoreis not overwritten, and that worktree stays unclean;.ultrafuzz;generated-workflow-verifier.test.ts: adds the helper to the no-op stubs of the test that extractsprepareArtifactMirror.unstableon this machine because it assumes git's default branch ismaster. After the follow-up commit I reran the new tests and the stubbed preparation test, and both pass.🤖 Generated with Claude Code
The PR is not yet safe to merge because successful tasks can still retain worktrees containing only accepted outputs or runtime-published companions.
Fix with agent prompt
Summary
The PR adds worktree-local ignore files so Smithers can reap successful task worktrees without ignoring files in the project checkout.
artifacts/rules to declared outputs, preserving worktrees with other agent files.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Task preparation] --> B[Write worktree-local ignore rules] B --> C[Task writes outputs and companions] C --> D[Git status checks worktree] D -->|Clean| E[Reap worktree] D -->|Untracked files remain| F[Keep worktree]Reviews (2) · Last reviewed commit: "fix(runtime): ignore only declared outpu..."