fix(runtime): init ignores generated state and says to commit project files (#1226) - #1261
mrthankyou wants to merge 2 commits into
Conversation
… files (#1226) Campaigns default to private, and a private launch requires a clean Git target, but init wrote its files into the target without touching .gitignore or saying to commit them. A first-time user's first run failed with DATA_GOVERNANCE_PRIVATE_TARGET_UNBOUND. init now adds a managed block to the root .gitignore for the state runs generate (.ultrafuzz/runs, workspaces, cache, evals/runs, modal/results, audit logs, the engine's generated directories and database), refreshes it in place on every init, and reports INIT_COMMIT_PROJECT_FILES. The project files themselves stay unignored: they are run inputs and part of the recorded target identity. A .gitignore init cannot write safely yields INIT_GITIGNORE_NOT_UPDATED instead of failing init. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| const existing = | ||
| lstatIfPresent(filePath) === undefined | ||
| ? undefined | ||
| : readStableInitReviewFile(projectRoot, filePath, MAX_GITIGNORE_BYTES, "project .gitignore").toString("utf8"); |
There was a problem hiding this comment.
Non-UTF-8 ignore rules are corrupted
If an existing .gitignore contains non-UTF-8 bytes, init decodes the whole file as UTF-8 and then rewrites it as UTF-8. Invalid byte sequences are replaced, permanently changing user-owned rules outside the managed block. Preserve those bytes or decline to rewrite a file that cannot be decoded without loss.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/init.ts
Line: 276
Comment:
**Non-UTF-8 ignore rules are corrupted**
If an existing `.gitignore` contains non-UTF-8 bytes, `init` decodes the whole file as UTF-8 and then rewrites it as UTF-8. Invalid byte sequences are replaced, permanently changing user-owned rules outside the managed block. Preserve those bytes or decline to rewrite a file that cannot be decoded without loss.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| try { | ||
| ignoreGeneratedState(projectRoot, created, preserved, overwritten); | ||
| } catch (error) { |
There was a problem hiding this comment.
Failed writes can erase ignore rules
If a write fails after an existing .gitignore has been truncated, such as when the filesystem fills, this catch turns the error into a warning and reports a successful init. The user's ignore file can be left partly written or empty. Keep the original intact until the replacement is complete, and do not report a partial write as success.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/init.ts
Line: 199-201
Comment:
**Failed writes can erase ignore rules**
If a write fails after an existing `.gitignore` has been truncated, such as when the filesystem fills, this catch turns the error into a warning and reports a successful `init`. The user's ignore file can be left partly written or empty. Keep the original intact until the replacement is complete, and do not report a partial write as success.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const begin = existing.indexOf(GENERATED_STATE_IGNORE_BEGIN); | ||
| const end = begin < 0 ? -1 : existing.indexOf(GENERATED_STATE_IGNORE_END, begin); | ||
| if (begin >= 0 && end >= 0) { | ||
| contents = `${existing.slice(0, begin)}${block}${existing.slice(end + GENERATED_STATE_IGNORE_END.length)}`; |
There was a problem hiding this comment.
Marker text can replace user content
If an existing .gitignore mentions both marker strings within a comment or pattern, the substring search treats that text as a managed block. Rerunning init then replaces the text between the markers, changing user-owned content and creating avoidable cleanup work. Only standalone marker lines should delimit the block.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/init.ts
Line: 281-284
Comment:
**Marker text can replace user content**
If an existing `.gitignore` mentions both marker strings within a comment or pattern, the substring search treats that text as a managed block. Rerunning `init` then replaces the text between the markers, changing user-owned content and creating avoidable cleanup work. Only standalone marker lines should delimit the block.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const GENERATED_STATE_IGNORE_ENTRIES = [ | ||
| "/.ultrafuzz/runs/", |
There was a problem hiding this comment.
Custom run directories remain unignored
If run.output_dir is set to a project-local path such as analysis-output, this block still ignores only .ultrafuzz/runs/. Run files then appear as untracked in git status, despite the guidance that a run leaves the target clean. The governance check separately excludes the selected run root, so it can report a clean identity while Git status is dirty. Account for the configured directory or qualify the guidance.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/init.ts
Line: 236-237
Comment:
**Custom run directories remain unignored**
If `run.output_dir` is set to a project-local path such as `analysis-output`, this block still ignores only `.ultrafuzz/runs/`. Run files then appear as untracked in `git status`, despite the guidance that a run leaves the target clean. The governance check separately excludes the selected run root, so it can report a clean identity while Git status is dirty. Account for the configured directory or qualify the guidance.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.#1226) init now always reports INIT_COMMIT_PROJECT_FILES (info). The test still requires that a refreshing init reports no warning or error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for this, and for the careful write-up in #1226. We're closing it because we've decided to remove data governance from Ultrafuzz entirely (#1269). The clean-target requirement that One finding from review, for the record: the benchmark workers run |
Fixes #1226.
Problem
Campaigns default to
private, and a private launch requires a clean Git target.ultrafuzz initwroteultrafuzz.toml,.ultrafuzz/and.smithers/into the target without touching.gitignoreor saying to commit them. A first-time user's firstrunfailed withDATA_GOVERNANCE_PRIVATE_TARGET_UNBOUNDbefore any agent started.Confirmed on
unstable(5c1a9775): right afterinitin a clean repository,targetIdentityreportsdirty: true.Fix
This takes the issue's first option. The second option, counting the project files as Ultrafuzz-owned, is only safe if they stay part of the recorded identity, and ignoring them would remove them from it.
.gitignoreblock:initadds a block between# BEGIN Ultrafuzz generated stateand# END Ultrafuzz generated stateto the target's root.gitignore, creating the file if needed. It covers only generated state:.ultrafuzz/{runs,workspaces,cache,evals/runs,modal/results}/and.ultrafuzz/*-audit.jsonl;.smithers/{node_modules,workflows,continuations,logs,executions}/;smithers.db*.Every
initrefreshes the block in place and keeps every other line byte for byte. If the block is current, the file is untouched.Commit note:
initreportsINIT_COMMIT_PROJECT_FILES(info), telling the user to commit what it created before a private campaign.Project files stay tracked: the config, topology, prompts, references and agent registry are run inputs, so they stay part of the recorded target identity.
Root
.gitignore, not one inside.ultrafuzz/:smithers.dblives in the project root. Also, a committed.ultrafuzz/.gitignorewould stop fix(runtime): ignore Ultrafuzz's own worktree roots so successful runs reap task worktrees (#1227) #1259 (keep_workspaces = falsenever reaps task worktrees, andcleanleaves worktrees, branches and the engine run record behind #1227) from writing its worktree ignore file, so worktrees would never be cleaned up.Fallback: if
.gitignorecan't be written safely, for example because it's a symlink,initstill succeeds and reportsINIT_GITIGNORE_NOT_UPDATED(warning).No engine name in output: neither message names the workflow engine, which the CLI's product-surface test enforces.
Docs: first-campaign tutorial (with the commit step), CLI reference, CHANGELOG.
Tests
New
data-governance.test.tstests:initkeeps an existing line and adds the block, and the advice isinfo;git statusand the identity unchanged;initpreserves.gitignore..gitignore: warning, and nothing is written through the link.CLI: the existing
inittests pass, includinginit and validate emit schema-versioned launch JSON, which caught the engine name in an earlier wording.Smoke test through the built CLI, on Damn Vulnerable DeFi v4.1.0 trimmed to Unstoppable:
init, then commit, thenvalidate --audit-profile smokepasses.model:anthropic.git statusstays clean with the run's files on disk.The campaign was still running when this PR was opened.
Local failures:
unstableon this machine, because they pick up the local~/.codexmodel route.report bundle creates a portable ZIP without workspacesfails here because the case-insensitive macOS filesystem merges two files whose names differ only in case. It doesn't touchinit, but I didn't rerun it onunstable.The full CLI test file did not finish locally within 40 minutes, so CI is its first full run.
🤖 Generated with Claude Code
The PR is not yet safe to merge because the previously reported
.gitignorecorruption and failed-write risks remain.Fix with agent prompt
Summary
The PR adds a managed root
.gitignoreblock for generated run state and advises users to commit project files before a private campaign.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[init] --> B[Create or refresh managed .gitignore block] B --> C[Advise user to commit project files] C --> D[Clean Git target for private run]Reviews (2) · Last reviewed commit: "test(runtime): expect init's commit advi..."