Skip to content

fix(runtime): hydrate unpinned task worktrees from a commit-keyed dependency cache (#1251) - #1287

Merged
aviggiano merged 15 commits into
unstablefrom
fix/1251-unpinned-submodule-hydration
Oct 15, 2026
Merged

aviggiano merged 15 commits into
unstablefrom
fix/1251-unpinned-submodule-hydration

Conversation

@mrthankyou

@mrthankyou mrthankyou commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

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:

  • 342 artifact tests and 30 targeted runtime tests for schema/publication safety.
  • 34 focused tests for cache, restoration, and lifecycle behavior before integration; 19 targeted tests on the final integrated head.
  • Regression demonstrations fail on the old code for unusual filenames and concurrent cache replacement.
  • Runtime build, test compilation, strict changed-lines lint, formatting, schema parity, and documentation checks passed.
  • All 17 checks passed on the final commit before merge: https://github.com/monad-developers/ultrafuzz/actions/runs/38658615722 .

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.

RetriggerView in GreptileConfidence 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.

  • Ordinary task worktrees get submodules from a cache keyed by the launch checkout.
  • Ordinary tasks record dependency edits, restore files, and carry the record into reports.

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| B
Loading

Reviews (4) · Last reviewed commit: "Merge unstable after reviewed bugfixes" · Reviewed by Greptile

…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>
@mrthankyou
mrthankyou requested a review from a team as a code owner October 8, 2026 16:40
Comment thread packages/runtime/src/pinned-submodules.ts Outdated
Comment thread packages/artifacts/src/runtime-schemas.ts Outdated
Comment thread packages/runtime/src/checkout-submodules.ts Outdated
Comment thread packages/runtime/src/pinned-submodules.ts Outdated
Comment thread packages/runtime/src/pinned-submodules.ts Outdated
Comment thread packages/runtime/src/templates/smithers/workflows/workflow.tsx
thankyou and others added 12 commits October 8, 2026 13:38
- 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>
Comment thread packages/runtime/src/pinned-submodules.ts Outdated
Comment thread packages/runtime/src/pinned-submodules.ts
@aviggiano
aviggiano merged commit 4a412a7 into unstable Oct 15, 2026
17 checks passed
@aviggiano
aviggiano deleted the fix/1251-unpinned-submodule-hydration branch October 15, 2026 00:47
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.

Task worktrees of unpinned runs have uninitialized lib/ submodules, so forge clones dependencies over the network

2 participants