🤖 fix(ssh): harden bare base repo against receive-pack thin-pack failures#3356
Merged
Conversation
…ures See PR body for details.
Collaborator
Author
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
mux-bot Bot
added a commit
that referenced
this pull request
May 21, 2026
…RYABLE_ERRORS #3356 introduced two adjacent constants in SSHRuntime.ts that listed the same four receive-pack thin-pack failure patterns inline: PROJECT_SYNC_RETRYABLE_ERRORS (used by the broad retry classifier) and UNRESOLVED_DELTA_PUSH_PATTERNS (used by isUnresolvedDeltaPushFailure to decide whether the next retry attempt should add --no-thin). Define the four-string sub-class once on UNRESOLVED_DELTA_PUSH_PATTERNS and spread it into PROJECT_SYNC_RETRYABLE_ERRORS so the lists can never drift. .some(pattern => errorMsg.includes(pattern)) sees the same patterns in the same order, so the retry classification is byte-equivalent — pure DRY, no behavior change.
mux-bot Bot
added a commit
that referenced
this pull request
May 22, 2026
…RYABLE_ERRORS #3356 introduced two adjacent constants in SSHRuntime.ts that listed the same four receive-pack thin-pack failure patterns inline: PROJECT_SYNC_RETRYABLE_ERRORS (used by the broad retry classifier) and UNRESOLVED_DELTA_PUSH_PATTERNS (used by isUnresolvedDeltaPushFailure to decide whether the next retry attempt should add --no-thin). Define the four-string sub-class once on UNRESOLVED_DELTA_PUSH_PATTERNS and spread it into PROJECT_SYNC_RETRYABLE_ERRORS so the lists can never drift. .some(pattern => errorMsg.includes(pattern)) sees the same patterns in the same order, so the retry classification is byte-equivalent — pure DRY, no behavior change.
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.
Summary
Hardens the SSH runtime's shared bare base repo (
.mux-base.git) against the receive-packunresolved deltas left after unpacking/unpack-objects abnormal exitfailure that aborts workspace creation. The fix layers four independent defenses so that any one of them is enough to keep the slow-path push healthy on both new and legacy remote repos.Background
A user hit this during SSH workspace init:
Two interacting root causes:
transfer.unpackLimitobjects (default100), receive-pack routes objects throughunpack-objectsinstead ofindex-pack.unpack-objectscannot resolve thin-pack delta bases that live in other on-disk packs, so a small thin push to a populated bare repo can fail withunresolved deltas left after unpackingeven when every base object is technically present.git gcthat gets SIGKILLed between writingpack-<sha>.packandpack-<sha>.idxleaves the pack with no index. Git's negotiation surface ignores indexless packs, so the server doesn't advertise objects it actually has on disk → the client builds a thin pack assuming those bases are missing → receive-pack fails to resolve them.The pre-fetch from origin (which would have populated the bases) had also failed, exposing the underlying weakness.
The existing retry classifier only matched generic transport errors (
pack-objects died,Connection reset, …), so a thin-pack failure surfaced immediately as a fatalFailed to sync project.Implementation
Four layered defenses in
src/node/runtime/SSHRuntime.ts:ensureBaseRepo's batched setup script now also runsgit config --local receive.unpackLimit 1. Receive-pack always routes incoming pushes throughindex-pack(which calls--fix-thinand resolves missing delta bases from local objects) instead ofunpack-objects, regardless of object count. Idempotent — runs every sync, so legacy repos heal on the next workspace creation. Trade-off: more small packs, but the existing fragmented-pack maintenance (ensureHealthyBaseRepoForSync) already coalesces them on a threshold.repairBaseRepoForSyncgets a new step beforegc: scanobjects/pack/pack-*.packfor orphans missing.idxand rungit index-packto rebuild the index from the pack. The existing code comment already noted this exact SIGKILL failure mode but didn't act on it.unresolved deltas,unpacker error,unpack-objects abnormal exit,remote unpack failedtoPROJECT_SYNC_RETRYABLE_ERRORSso the existing retry loop kicks in (which runs the maintenance from Better authentication UX #1 + ipc: use consistent error propogation #2 before re-attempting).forceNoThinNextAttemptflag and threads it intosyncProjectSnapshotViaGitPushas{ forceNoThin: true }. The next push then includes--no-thinon both the branch and tag pushes, sending a self-contained pack the receiver can unpack without any delta-base lookup. Unrelated retryable failures (connection reset, killed by signal, …) leave the flag off — no bandwidth penalty for problems--no-thinwouldn't fix.Also surfaces
Pre-fetch from origin skipped (fetch failed)cause vialog.debugso we can diagnose recurrent prefetch misses without spamming the init logger on the happy path.Validation
make static-checkpasses (typecheck + lint + format + docs links).bun test src/node/runtime/passes (all SSH runtime / sync contract / retry orchestration suites).--no-thin; connection-reset → flag stays off.--no-thinappears on both branch and tag pushes whenforceNoThin: true; default sync path omits--no-thin.Risks
Touches the SSH workspace init slow path, which runs on every cold SSH workspace creation and every snapshot-drift sync.
receive.unpackLimit=1is unconditionally applied to every shared bare repo viaensureBaseRepo. The trade-off is more small packs vs. fewer thin-pack-unpack failures; existing maintenance already collapses fragmented packs so the steady-state is unchanged.git index-packagainst any.packlacking an.idx. A corrupt.packwill failindex-pack(best-effort, logged), then fall through togcand the--no-thinretry push.--no-thinis only applied on retry after a confirmed thin-pack failure, so the happy path is unchanged.Generated with
mux• Model:anthropic:claude-opus-4-7• Thinking:high• Cost:$6.77