Repository navigation
fix(runtime): hydrate unpinned task worktrees from a commit-keyed dependency cache (#1251) - #1287
Merged
Merged
Conversation
…endency cache (#1251) Task worktrees of an ordinary, unpinned run had empty lib/ submodules, so the first forge build or test cloned dependencies over the network. Only runs launched from the invariant-pinned branch were hydrated. Launch now records the checkout's initialized submodule files and their hashes and copies them into a commit-keyed cache in the repository's Git directory (.git/ultrafuzz/submodule-cache/<manifest-sha256>). Tasks hydrate from that cache instead of the sealed execution snapshot, which no longer carries dependency files for any run (#921). The cache is a record checked on every read: a missing or damaged entry is rebuilt from the submodules' Git objects at the recorded commits, never the working tree. Cached files are read-only. An ordinary checkout may keep its shared .git/modules and local submodule.* config; linked worktrees resolve submodule metadata per worktree and the task-scoped inert URL and update = none override the shared config. Local url.* rewrites are still refused. Uninitialized nested gitlinks are mirrored as empty directories. Record, don't enforce, for ordinary runs: - A checkout that cannot be recorded launches with a SUBMODULE_HYDRATION_UNAVAILABLE warning, as before. - A task that cannot be hydrated runs without dependencies and its verification marker records dependency_hydration_unavailable. - After the agent, edited dependency files are restored and recorded in the marker's dependency_changes; the runtime restates them in report.json run_metadata.dependency_changes and report.md carries a fixed notice. Pinned runs stay strict and still fail on either. Also fixes verifySealedFileClosure, which compared a depth-first walk with a sorted list and so rejected trees like OpenZeppelin's fv/ beside fv-requirements.txt, for pinned runs too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only a missing dependency source falls back to running without hydration. A changed task-local submodule setting, changed gitlinks, or an incomplete rollback now fail the task again instead of being recorded as "unavailable" with edited dependencies left in place. - Marker changed_paths accept any relative filesystem path (spaces, Unicode, long names) instead of the artifact safe-path rule, which failed tasks whose dependency file names it rejected. - Ordinary checkouts record their manifest by content hash, so launches on one commit with different nested-submodule state keep separate records and an earlier run can still rebuild its cache. - The cache refill reads each submodule's objects from its stored Git directory, so it works after `git submodule deinit`; the directory is found by the clone-time name or the path and must hold the recorded commit. - Post-agent comparison hashes only files whose size matches the recorded entry, streamed, so a large file an agent leaves behind is recorded without being read into memory. - The dependency record is persisted before files are restored and reloaded when a restarted check finds clean files, so a controller restart no longer loses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut invariant checks that cannot fail (#687) Recon runs in assertion mode: only a failed assert is a finding, and a call that reverts for any other reason, including a custom error, a failed require or an arithmetic panic, is discarded. Four invariant prompts claimed every reached revert was "the signal the campaign records", which steers agents toward oracles that cannot fail. They now state how assertion mode works, require assert for every invariant check, and describe the selector-aware liveness oracle: the typed call in try, the catalog's documented selectors accepted in catch, and assert(false) for any other failure (the part of #524 moved here). The campaign's verifier now warns, without failing the node, about generated test code Recon cannot fail: constant-true assertions, a require that checks an outcome after a call made for its effect, a catch that asserts nothing, and an assertion that computes its expected value with code imported from src/ or contracts/. The node that writes the finding lifecycle ledger carries these warnings to the final report, whose warnings companion lists them first under "Invariant checks that cannot fail", with their count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The assertion matcher was one global RegExp shared by every check. A selector-aware catch left its lastIndex past the catch, and matchAll starts from lastIndex, so assertions earlier in the file or in the next file went unchecked. Each search now builds its own matcher. - A catch that only rethrows is now a blanket catch: Recon discards the rethrown revert, so only an assertion in the catch can report a failure. - The report section is now "Invariant checks that may not be able to fail" and asks for review: the checks are heuristics, and an import or a constant-true branch does not prove a property untested. The implementation-call message says the same. - A truncation notice takes the gate of the warning that overflowed, so it was counted as a check. Only INVARIANT_ORACLE_* codes are counted now; the notice stays with the other warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ld a rejected report (#1217) The artifact secret gate matched configured credentials byte for byte, so a value holding a quote, a backslash, a tab or a line break never matched the JSON that escapes it, and a verified report could publish it. The gate now also scans a JSON publication's decoded strings, joined by NUL so adjacent fields never form one phrase. Configured values are also found percent-encoded, with Markdown backslash escapes, or broken across lines. The verifier, synchronization and the rejected-report check now match every credential an agent can see: the configured api_key_env variables and the allowlisted variables that hold a credential, not only names like *_API_KEY. The value of ULTRAFUZZ_PROVIDER_CREDENTIAL_ENV_NAMES, a list of names, is no longer treated as a credential. The Modal public bundle, eval diagnostics and recovery lifecycle checks reuse the same scan. A final report the gate rejects is withheld instead of failing the run: the report verifier tolerates failure in the engine, the failure records reason_code ARTIFACT_SECRET_GATE_REJECTED, the run ends succeeded when everything else did, status reports report-withheld-secret, and `resume --retry-failed` regenerates the report. Any other report verification failure, and a withheld report under --require-complete, still fail the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ped escaped credentials (#1217) Review follow-up: - The unchecked report fallback rescanned a rejected draft against the current environment, so once the credential it matched was unset or rotated, `ultrafuzz report` could publish the withheld draft. A report attempt whose failure records ARTIFACT_SECRET_GATE_REJECTED now stays unavailable until a fresh attempt replaces it. - The line-break check compared a configured value only against the Markdown-unescaped text, which had already lost the value's own backslash. The text as written is now checked too. - The compileSmithersWorkflow continueOnFail count expects the report verifier's own continue policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ordinary task worktrees now receive the launch checkout's initialized submodule files from a shared, content-addressed dependency cache. This fixes empty dependency directories that made task-local Forge commands clone dependencies over the network. Fixes #1251.
Each read verifies the cache against the recorded manifest. Missing or damaged entries are rebuilt from Git objects at the recorded commits. Persisted pre-cache workflows keep their historical runtime entry points, and older pinned runs can recover the exact dependency bytes from their recorded execution snapshot when no Git objects remain. New execution snapshots omit dependency copies.
Ordinary tasks restore dependency edits and record the changed paths and exact count; pinned tasks continue to reject edits. Runtime reports restate the records and display a fixed notice. Credential-bearing filenames are omitted before durable recording, including pending records, so restart or credential rotation cannot expose them later. Ordinary filenames remain visible, and a path list may be empty while its exact change count remains positive.
The final implementation also preserves both invariant warnings and dependency metadata in verification markers, handles filesystem-valid unusual filenames during restoration, and serializes cache replacement so concurrent repair cannot remove another publisher's valid entry. Dead-owner recovery uses the existing journal lock. Historical workflow verification checks worktree bindings before any cache write.
Validation:
Limits: dependency records describe edits still present when the agent finishes; they do not detect edits already reverted by the agent. Cache entries are not pruned automatically. Gitlink changes still fail validation, and pinned runs retain strict dependency checks.


Confidence Score: 5/5
The PR appears safe to merge; both numbered previous findings are fixed.Summary
Task worktrees now get recorded submodule files from a shared dependency cache.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Launch records dependency files and hashes] --> B[Shared dependency cache] B --> C[Hydrate task worktree] C --> D[Agent runs] D --> E{Pinned run?} E -->|Yes| F[Reject dependency edits] E -->|No| G[Save edit record and restore files] G --> H[Verification marker] H --> I[Report metadata and fixed notice] J[Recorded Git objects] -->|Rebuild missing cache| B K[Historical execution snapshot] -->|Recover older runs| BReviews (4) · Last reviewed commit: "Merge unstable after reviewed bugfixes" · Reviewed by Greptile