gabriel / musehub public
fix BREAKING fix/228-repair-commit-body-size-limit #1 / 1
AI Agent gabriel · 2 days ago · Oct 22, 2026 · Diff

fix: repair-commit/repair-snapshot/repair-object get right-sized body limits (musehub#228)

repair-commit's payload is a single WireCommit record (commit metadata + structured_delta), not a full mpack — it fell under ContentSizeLimitMiddleware's generic 10 MB API_MAX_BYTES cap, not the 500 MB PUSH_MAX_BYTES reserved for push endpoints. A commit whose structured_delta is large enough (observed: 15.6 MB JSON payload on gabriel/muse commit sha256:2f17e90958a3a39216322972274314b4c495ed995ea0edf4d24a777d171226f7, the one commit out of 1,080 corrupted-signature rows the muse#107/#108 bulk repair effort could not fix) got rejected with 413 before the handler ever ran.

Phase 1 audit (beyond the ticket's own scope) found repair-object and repair-snapshot share the identical exposure: - repair-object carries arbitrary raw object bytes — fundamentally unbounded by nature, the same risk profile as a push payload, not a small metadata record. - repair-snapshot carries a manifest (path -> object_id for every tracked file) — bounded by file count, not raw content, but can still exceed 10 MB for a repo with enough files.

Design confirmed with gabriel: three tiers, not two. PUSH_MAX_BYTES (500 MB) — push/*, repair-object (joins push tier: raw bytes, push-scale risk) REPAIR_MAX_BYTES (100 MB) — repair-commit, repair-snapshot (new tier: metadata payloads, ~6x headroom over the observed 15.6 MB worst case without reaching push-scale) API_MAX_BYTES ( 10 MB) — everything else, unchanged

Sized REPAIR_MAX_BYTES from real data, not an arbitrary guess: queried the local dev DB directly (pg_column_size on musehub_commits.structured_delta, length(manifest_blob) on musehub_snapshots) to confirm realistic scale before picking 100 MB as comfortable headroom over the one known 15.6 MB production case.

nginx's client_max_body_size (500m, deploy/nginx-cf.conf) is unaffected — 100 MB is below it, so nothing changes there.

New tests/test_repair_commit_body_size_limit.py (8 tests): the exact 15.6 MB reproduction case now passes, 20 MB on repair-commit/repair-snapshot (over old API cap, under new repair cap) allowed, >100 MB on repair-commit/ repair-snapshot still correctly rejected (ceiling isn't removed, only right-sized), repair-object allowed up to 150 MB (over repair tier, under push tier) and still rejected over 500 MB.

Confirmed genuine RED: reverting the middleware change makes the whole test file fail to even import (REPAIR_MAX_BYTES doesn't exist) — restored, all 8 green.

Regression: 43 tests across repair/content-size/wire-push suites, plus 131 across the full wire fetch/push/tags suite — all green, no regressions.

Not yet done (deferred per the agreed every-5-tickets staging deploy cadence): Phase 5's live verification — running deploy/repair_commit_signatures.py against staging's gabriel/muse to confirm the one remaining corrupted-signature commit now repairs end-to-end, and a follow-up `muse verify` showing 0 signature failures. This requires the fix to actually be deployed to staging first; will run as part of the next batched staging deploy (2 of 5 since musehub#229).

sha256:12b077920a78fb8440555add7bdea5509ff80049f1e4cba9cdcebb4ea69c51fc sha
+16 ~2 −1 symbols
2 changed · 1099 in snapshot files
sha256:aae026462a81a82d39b35050cac171d976382f8a89d5149fc1779e322add4671 snapshot
+16
symbols added
~2
symbols modified
−1
symbol removed
2
files changed
1099
files in snapshot
0
dead code introduced
Semantic Changes 19 symbols
+ API_MAX_BYTES import import API_MAX_BYTES L27–27
+ AsyncClient import import AsyncClient L25–25
+ PUSH_MAX_BYTES import import PUSH_MAX_BYTES L27–27
+ REPAIR_MAX_BYTES import import REPAIR_MAX_BYTES L27–27
+ annotations import import annotations L23–23
+ test_limits_are_correct_values function function test_limits_are_correct_values L30–34
+ test_repair_commit_15_6mb_structured_delta_not_rejected_by_middleware function async_function test_repair_commit_15_6mb_structured_delta_not_rejected_by_middleware L42–57
+ test_repair_commit_over_api_limit_but_under_repair_limit_allowed function async_function test_repair_commit_over_api_limit_but_under_repair_limit_allowed L60–70
+ test_repair_commit_over_repair_limit_still_rejected function async_function test_repair_commit_over_repair_limit_still_rejected L73–85
+ test_repair_object_over_push_limit_still_rejected function async_function test_repair_object_over_push_limit_still_rejected L137–146
+ test_repair_object_over_repair_limit_but_under_push_limit_allowed function async_function test_repair_object_over_repair_limit_but_under_push_limit_allowed L122–134
+ test_repair_snapshot_over_api_limit_but_under_repair_limit_allowed function async_function test_repair_snapshot_over_api_limit_but_under_repair_limit_allowed L93–102
+ test_repair_snapshot_over_repair_limit_still_rejected function async_function test_repair_snapshot_over_repair_limit_still_rejected L105–114
~ musehub/middleware/content_size.py .py 3 symbols added, 1 symbol removed, 2 symbols modified
− _PUSH_PATH_RE variable variable _PUSH_PATH_RE L23–23
+ REPAIR_MAX_BYTES variable variable REPAIR_MAX_BYTES L42–42
+ _PUSH_SCALE_PATH_RE variable variable _PUSH_SCALE_PATH_RE L34–36
+ _REPAIR_PATH_RE variable variable _REPAIR_PATH_RE L39–39
Files Changed
+1 ~1
1099 in snapshot
← Older Oldest on fix/228-repair-commit-body-size-limit
All commits
Newer → Latest on fix/228-repair-commit-body-size-limit

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:12b077920a78fb8440555add7bdea5509ff80049f1e4cba9cdcebb4ea69c51fc --body "your comment"