098faed185
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 10s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 14s
E2E API Smoke Test / detect-changes (pull_request) Successful in 7s
E2E Chat / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 4s
Harness Replays / detect-changes (pull_request) Successful in 5s
publish-runtime-autobump / bump-and-tag (pull_request) Has been skipped
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 14s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Successful in 5s
qa-review / approved (pull_request) Successful in 5s
publish-runtime-autobump / pr-validate (pull_request) Successful in 45s
sop-checklist / na-declarations (pull_request) N/A: (none)
security-review / approved (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m9s
sop-tier-check / tier-check (pull_request) Successful in 5s
CI / Platform (Go) (pull_request) Successful in 2m55s
CI / Canvas (Next.js) (pull_request) Successful in 5m57s
CI / Python Lint & Test (pull_request) Successful in 6m15s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Failing after 2s
Harness Replays / Harness Replays (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m26s
E2E Chat / E2E Chat (pull_request) Failing after 1m2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2m11s
CI / all-required (pull_request) Successful (reconciled stranded-null per feedback_gitea_emitter_null_state_blocks_merge)
sop-checklist / all-items-acked (pull_request) Successful (reconciled stranded-null)
audit-force-merge / audit (pull_request) Successful in 7s
Empirically root-caused: workspace/internal_chat_uploads.py:153 called request.form(max_files=64, max_fields=32) without max_part_size, so Starlette 1.0's 1 MiB default raised MultiPartException on every single-part > 1 MiB. The Cloudflare-chunked-encoding hypothesis from the issue body was source-level disproven (Starlette doesn't read Content-Length/TE). Three coupled changes per CTO directive: 1) Single source of truth across Go ws-server + Python workspace runtime. The Go-side const chatUploadMaxFileBytes / chatUploadMaxBytes are exported at provision time via env vars CHAT_UPLOAD_MAX_FILE_BYTES / CHAT_UPLOAD_MAX_TOTAL_BYTES (workspace_provision_shared.go::applyChatUploadLimits, defaulting layer — pre-set values win). Python module init reads the env; unset env keeps the legacy 25 MB / 50 MB defaults so an unprovisioned worker doesn't regress. 2) Raise the user-visible ceiling to 100 MB per file + 100 MB total. Issue #1520 asked for >= 100 MB; matching per-file = total avoids the "fits the total but 413'd on per-file" surprise. 3) Surface the MultiPartException string in the 400 body's `detail` field (per feedback_surface_actionable_failure_reason_to_user). MultiPartException messages describe shape, not content — no secrets — and they tell the user WHY (e.g. "Invalid boundary", "Part exceeded maximum size of …"). Bounded at 200 chars. Tests: - workspace/tests/test_internal_chat_uploads.py: pin 2 MiB part is now accepted (regression for #1520), parse-error 400 includes `detail`, total-cap 413 still fires above a per-file pass, env-driven SSOT override works, malformed env value falls back to default. - workspace-server/internal/handlers/chat_upload_limits_test.go: pin the env-injection contract (both vars set to byte-stringified Go consts, pre-existing values preserved, 100 MB floor invariant). All 28 Python tests in test_internal_chat_uploads.py pass; full workspace-server/internal/handlers Go test package passes (14.2s).