Skip to content

perf(#1032): use blobless fetch with timeout for pre-review clone deepening - #1033

Open
fullsend-ai-coder[bot] wants to merge 3 commits into
mainfrom
agent/1032-blobless-fetch-timeout
Open

perf(#1032): use blobless fetch with timeout for pre-review clone deepening#1033
fullsend-ai-coder[bot] wants to merge 3 commits into
mainfrom
agent/1032-blobless-fetch-timeout

Conversation

@fullsend-ai-coder

@fullsend-ai-coder fullsend-ai-coder Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add --filter=blob:none to the git fetch --unshallow in pre-review.sh so only commit and tree objects are fetched — blobs are not needed for Tier 2 risk assessment (git log, tree-only diffs, --grep)
  • Wrap the fetch in timeout --kill-after=5 120 so a slow pack generation falls through to the existing degraded-signal warning instead of consuming the review budget
  • Switch from bare URL to named remote origin (required for --filter; configured by actions/checkout; -c http.extraheader auth works unchanged)
  • Set diff.renames=false in the deepened clone and make the Tier 2 change-coupling command blob-free, so the risk sub-agent can still read history inside the sandbox
  • Cover the deepening block with tests (it previously had none)

Context

Since #861, pre-review.sh auto-defaults REVIEW_GIT_FETCH_DEPTH=0 when risk assessment is enabled, running git fetch --unshallow on the target repo. On the fullsend e2e fixture repos (~1.6–1.8 GB), this adds 1–18 minutes per review run and intermittently breaks the behaviour suite's 12-minute dispatch-settle window (fullsend#6523).

Why the extra commit

A blobless clone registers origin as a promisor remote: git will lazily fetch any blob it needs. The review sandbox cannot serve that fetch — its egress policy allows the GitHub REST API, not the git wire protocol — so the fetch blocks until the run's timeout. Rename detection is the one operation in the Tier 2 command set that reads blob content, because inexact (similarity) detection has to compare file contents. Measured on a real repository with the promisor pointed at a nonexistent path: 3 of 4 rename commits made git show --name-only fail; the remaining one was an exact rename, which git resolves from the blob hash alone.

Fix, in two layers:

  1. Correctnessdiff.renames=false in the deepened clone, and the change-coupling command switched from git show --name-only to git diff-tree -r --no-commit-id --name-only --no-renames -m --root. A rename reported as delete+add still names both paths, so the coupling signal is unchanged; -m --root additionally repairs two pre-existing undercounts, since git show --name-only printed nothing for merge commits and only the destination path for exact renames.
  2. Containment — the fetch also leaves remote.origin.promisor and remote.origin.partialclonefilter in the .git/config that ships to the sandbox. Both are unset afterwards, unconditionally (they are written during fetch setup, so a part-way failure registers them too). Both are required: partialclonefilter alone re-creates the promisor remote. With them gone, a command that does need a historical blob fails in 0s with unable to read object instead of hanging until the run's 20-minute timeout.

GIT_NO_LAZY_FETCH was rejected as the control: it landed upstream in git 2.45, and the sandbox image is 1:2.43.0-1ubuntu7.3 — it works there only via an Ubuntu backport, which a base-image change would silently remove.

Verification

Measured, not assumed:

Claim Result
--filter=blob:none is honoured on --unshallow Yes, via a configured remote: .git 59M → 24M, history 1 → 5762 commits
...with a bare URL instead Silently ignored — all blobs fetched, no promisor config. This is why origin is required
All Tier 2 git log shapes on a blobless clone Blob-free (verified against an unreachable promisor)
git show --name-only on inexact-rename commits Needs blobs → fails/blocks; --no-renames and diff.renames=false both fix it, identical file lists
Repo state after a killed fetch Stays shallow, git fsck clean, retry succeeds — the sub-agent's shallow check handles it
origin identity actions/checkout runs git remote add origin https://github.com/<source_repo>, so it is the target repo over HTTPS
Ref corpus after the switch Widens from 1 ref to 93 branches + 72 tags (git log --all 5,807 → 6,019 commits) — an input to Tier 2 revert frequency, disclosed here and in-code
Unsetting only remote.origin.promisor Insufficientpartialclonefilter re-registers the remote, so both keys must go

Testing

  • bash scripts/pre-review-test.sh — all pass, including 18 new deepening assertions that run the real --unshallow --filter=blob:none fetch against a local bare repo (no network) and assert the clone ends up non-shallow, blobless, rename-detection-off, and readable with the promisor pointed at a nonexistent path
  • Includes a negative control asserting rename detection would need the network on that fixture — so the assertions cannot go vacuous if diff.renames=false is dropped
  • make check-bundle confirms the bundled pre-review.sh matches the source
  • Note: scripts/post-retro-test.sh fails 16 GitLab/curl tests on this workstation, identically on pristine origin/main — pre-existing and unrelated

Closes #1032

…pening

The git fetch --unshallow in pre-review.sh transferred all objects
including blobs, adding 1–18 minutes per run on large repos. Tier 2
risk assessment only reads commits and trees (git log, git show
--name-only, --grep), so blobs are wasted bandwidth and disk.

Two changes:
- Add --filter=blob:none to fetch only commit and tree objects,
  using the named remote `origin` (required for --filter, configured
  by actions/checkout). The -c http.extraheader auth works unchanged.
- Wrap the fetch in `timeout 120` so a slow server falls through to
  the existing degraded-signal warning path instead of consuming the
  review budget.

Closes #1032
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 25, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:17 PM UTC · Completed 9:29 PM UTC

Commit: 0d07626 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.14

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Aug 25, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 25, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

PR grew from 2 files/8 lines to 5 files/379 lines since prior assessment, but 206 of the new lines are tests, keeping test ratio at 0.20 and mitigating size risk; 4 protected paths elevate Tier 1 change-size composite to 3, and high regression history on pre-review.sh (10 avg fix/revert commits in 90d) elevates Tier 2, but bot authorship, zero security-sensitive files, no CI/dependency changes, and well-scoped issue alignment keep the weighted composite at 2.1, rounding to moderate (2).

Previous run

Risk Assessment: moderate (2/5)

Details

Small, well-scoped bot PR (2 files, 8 lines) matching issue requirements exactly, but protected paths and zero test coverage elevate Tier 1, and high recent regression history on pre-review.sh elevates Tier 2, yielding moderate overall risk.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review

Findings

Medium

Low

  • [scope-expansion] skills/pr-risk-assessment/SKILL.md:92 — The change-coupling command switch from git show --name-only to git diff-tree -r --no-commit-id --name-only --no-renames -m --root includes -m and --root flags that fix two pre-existing undercounts (merges printed nothing, initial commit was excluded). These correctness improvements go beyond the strict blobless-fetch requirement (--no-renames alone suffices), though folding them in while rewriting the same command is pragmatic.
  • [naming-convention] scripts/pre-review-test.sh:668 — The test name string "disabled-risk-l..." is truncated with an ellipsis, unlike every other test name in the file which uses a complete descriptive name (e.g., "explicit-depth-leaves-clone-shallow"). This appears to be an accidental truncation.
Previous run

Review

Findings

Medium

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 25, 2026
The blobless deepen leaves origin registered as a promisor remote, so git
lazily fetches any blob it needs. The review sandbox cannot serve that
fetch — its egress policy allows the GitHub REST API, not the git wire
protocol — so the fetch blocks until the run's timeout.

Rename detection is the one thing in the Tier 2 command set that reads
blob content: inexact (similarity) detection has to compare file contents,
and 3 of 4 rename commits sampled from a real repository failed this way
against an unreachable promisor. Exact renames are resolved from the blob
hash alone, which is why a fixture that renames without editing would not
catch this.

- set diff.renames=false in the deepened clone, so every Tier 2 history
  command is blob-free; a rename reported as delete+add still names both
  paths, so the coupling signal is unchanged
- switch the SKILL.md change-coupling command from 'git show --name-only'
  to 'git diff-tree -r --no-commit-id --name-only --no-renames', and
  document that history must not be read with show -p/diff/log -p/blame
- add --kill-after=5 so a fetch ignoring SIGTERM is still bounded
- warn distinctly when GNU timeout is absent (local macOS runs) instead of
  reporting it as a fetch failure
- cover the deepening block with tests: it had none. They run the real
  fetch against a local bare repo, and assert the resulting clone is
  non-shallow, blobless, rename-detection-off, and that the coupling
  command runs with the promisor pointed at a nonexistent path

Assisted-by: Claude (fix), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09
waynesun09 force-pushed the agent/1032-blobless-fetch-timeout branch from f316a65 to e59212e Compare August 26, 2026 16:08
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 26, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 4:17 PM UTC · Ended 4:30 PM UTC

Commit: f316a65 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 26, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 4:31 PM UTC · Ended 5:06 PM UTC

Commit: e59212e · View workflow run →

@waynesun09

Copy link
Copy Markdown
Member

Two-model review (Claude + Grok) done on e59212e; CI green. The design holds up — --filter=blob:none genuinely is ignored with a bare URL (verified: all blobs fetched, no promisor config), so fetching via origin is required, not incidental. One follow-up is not yet in this branch and I want it recorded rather than lost:

Outstanding: contain the promisor registration (recommend before merge)

--filter=blob:none is not a transfer flag — it is a persistent repository mode. The fetch writes into the .git/config that ships to the sandbox:

[core]  repositoryformatversion = 1
[remote "origin"]
	promisor = true
	partialclonefilter = blob:none

…leaving ~14k objects missing (measured on fullsend-ai/fullsend). Git's own design note is explicit that this is out of contract here — Documentation/technical/partial-clone.txt: "Use of partial clone requires that the user be online and the origin remote … be available for on-demand fetching of missing objects." The review sandbox is by policy not online for git: policies/github/review.yaml grants egress to **/gh and **/node, not git/git-remote-https.

diff.renames=false (already on this branch) covers every command in the SKILL.md Tier 2 table. What it does not cover is anything the sub-agent improvises against history — git show -p, git log -p, git blame — which would block until the run's 20-minute timeout rather than failing.

Suggested addition to pre-review.src.sh, run unconditionally after the fetch (the keys are written during fetch setup, so a fetch that fails part-way still registers them):

git -C "${_TARGET_DIR}" config --unset remote.origin.promisor          || true
git -C "${_TARGET_DIR}" config --unset remote.origin.partialclonefilter || true

Both keys are required. Unsetting only promisor does not work — partialclonefilter re-creates the promisor remote (promisor-remote.c, v2.43.0). With both unset, the same command fails in 0s with fatal: unable to read <oid> instead of hanging; git log, git status and git diff-tree are unaffected. This must happen on the runner, since the sandbox mounts the repo readonly_repo: true.

GIT_NO_LAZY_FETCH is not a sound alternative: it landed upstream in git 2.45, and the sandbox image is 1:2.43.0-1ubuntu7.3. It happens to work there only because Ubuntu backported the env-var check — a distro patch that would silently stop working on a base-image change.

Also worth folding in

  • skills/pr-risk-assessment/SKILL.md — the coupling command on this branch is git diff-tree -r --no-commit-id --name-only --no-renames. Adding -m --root and | sort -u additionally fixes two pre-existing undercounts: git show --name-only printed nothing for merge commits, and only the destination path for exact renames, so both were being silently dropped from co-commit tallies.
  • The --is-shallow-repository guard in SKILL.md returns false after a successful blobless deepen, so it no longer detects "history unavailable". Worth widening to mention that historical file contents are absent by design and an unable to read object there is expected, not a broken clone.

Disclosure

Fetching by remote name also widens the ref corpus. Measured on fullsend-ai/fullsend from a fetch-depth: 1 checkout: 1 ref → 93 branches + 72 tags, and git log --all 5,807 → 6,019 commits. That is an input to Tier 2's revert-frequency signal, so it is a behaviour change in a PR framed as perf — arguably a signal improvement, but it should be a stated one. A comment to that effect is on the branch.

I authored the two fix commits here, so this needs a second pair of eyes on the approval rather than mine.

The blobless fetch does not just shrink the transfer — it converts the
target repo into a partial clone, writing remote.origin.promisor and
remote.origin.partialclonefilter into the .git/config that ships to the
sandbox, with the historical blobs absent. Git's own design note is
explicit that this mode 'requires that the user be online and the origin
remote ... be available for on-demand fetching of missing objects'. The
review sandbox is deliberately not online for git: the egress policy
grants the GitHub REST API to gh and node, not the git wire protocol. Any
command that reaches for a missing blob therefore blocks until the run's
timeout rather than failing.

diff.renames=false covers every command in the SKILL.md Tier 2 table, but
not one the sub-agent improvises (show -p, log -p, blame). Dropping the
registration turns those into an immediate 'unable to read object'.

Both keys must go: partialclonefilter on its own re-creates the promisor
remote. The unsets are unconditional because the keys are written during
fetch setup, so a fetch that fails part-way registers them too.

- unset both promisor keys after the fetch, success or failure
- scope the credential header to origin's own host, restoring the
  guarantee the literal-URL form used to give
- surface fetch stderr on failure: timeout, auth failure and a server
  refusing --filter are all actionable and otherwise look identical
- SKILL.md: add -m --root to the coupling command, fixing two
  pre-existing undercounts (merges printed nothing, exact renames named
  only the destination), and document that a missing-blob error is
  expected rather than a broken clone
- note in-code that fetching by remote name widens the ref corpus from
  one ref to all branches and tags, which feeds Tier 2 revert frequency
- tests: assert both keys are unset and that a historical blob is
  genuinely absent, rather than asserting the filter config that the
  containment now removes

Assisted-by: Claude (fix), Claude (review), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09

Copy link
Copy Markdown
Member

Containment landed in 1435e0d — both promisor keys are now unset after the fetch (unconditionally), the credential header is scoped to origin's host, fetch stderr is surfaced on failure, and the SKILL.md coupling command gained -m --root. Tests assert both keys are unset and that a historical blob is genuinely absent, rather than asserting the filter config the containment removes. Everything in my previous comment is now in the branch.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:08 PM UTC · Completed 5:28 PM UTC

Commit: 1435e0d · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $8.11

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread skills/pr-risk-assessment/SKILL.md
Comment thread scripts/pre-review-test.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review requires-manual-review Review requires human judgment risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

review: pre-review unshallow (#861) adds 1–18 min per run on large repos; breaks fullsend e2e settle window

1 participant