reject non-string model with 400 before resolver (#6407, 6/6). Reconciled with #6437 early schema validation on chat.ts. Integrated into release/v3.8.46.
Dynamic MITM manager import on the agent-bridge route + Turbopack static-analyzer anchor in the skills generator (#6329). Integrated into release/v3.8.46.
* fix(docker): make @/mitm/manager Turbopack stub opt-in (OMNIROUTE_MITM_STUB=1) so npm/Electron/VPS builds bundle the real manager (#6344)
v3.8.45 flipped the production bundler default to Turbopack. next.config.mjs
aliased @/mitm/manager to the Docker-only degraded stub unconditionally, which
was harmless while Docker was the only Turbopack consumer but shipped the stub
to every npm/Electron/VPS artifact once Turbopack became the default — breaking
Agent Bridge start with 'MITM manager stub reached at runtime'. The alias is now
gated on OMNIROUTE_MITM_STUB=1 (set only by the Dockerfile) via the shared
scripts/build/mitm-stub-flag.mjs helper.
Regression guard: tests/unit/mitm-stub-alias-6344.test.mjs (4).
* test(next-config): align mitm-manager alias assertion with opt-in stub (#6344)
The existing test asserted the alias was always present; #6344 makes it opt-in
(OMNIROUTE_MITM_STUB=1). Default build now asserts no alias, plus a new env-matrix
test covering both the default (no stub) and Docker (stub) cases.
* docs(changelog): restore #6359 bullet eaten by merge auto-resolve
Clients like the Codex app emit parameters:{type:null,...} for some tools;
OpenAI-compatible upstreams reject with '400 Invalid schema for function ...:
schema must be a JSON Schema of type object, got type null'. toolSchemaSanitizer
already dropped the null; it now re-adds the mandatory root object type (plus
empty properties / open additionalProperties when absent). Combinator roots
(anyOf/oneOf/allOf) and explicit root types are preserved.
Regression guard: 5 new cases in tests/unit/tool-schema-sanitizer.test.mjs.
Two defects found live in the v3.8.45 Phase 5 run (2026-07-06):
- execFileSync default 1 MiB maxBuffer crashed on git show origin/main:CHANGELOG.md
- the i18n resync only synced the [NEXT] (TBD) section, leaving the just-shipped
finalized section as '— TBD' in all 42 mirrors; now also syncs [prevVersion]
bounded by the heading below it (new pure helper versionAfter, unit-tested)
* test(ci): quarantine concurrency-sensitive flakes into a serial pass (tests/unit/serial/)
glm-coding-plan-monthly-3580, quota-division-blocks and provider-health-autopilot
fail under --test-concurrency>1 CPU contention but pass isolated (v3.8.45 release
benchmark: this class cost ~28min re-runs per CI round). They now live in
tests/unit/serial/ and run in a dedicated --test-concurrency=1 step appended to
every runner (test:unit, :ci, :ci:shard, :fast, :shard:1/2, coverage:runner —
sharded variants shard the serial pass too, so concurrent shard jobs never
self-collide). Discovery + TIA gates know the new glob; stryker.conf.json path
updated. Guard: tests/unit/test-serial-quarantine.test.ts (4 tests).
* test(ci): quarantine combo-health-autopilot too (async logger writes after test end under load)
Fresh evidence from this PR's own CI: all 3 subtests pass but an async log write
lands after the test ends (ENOENT app.log -> uncaughtException) — same
concurrency-flake family. Moved to tests/unit/serial/, imports adjusted,
stryker path updated, guard test now asserts 4 quarantined files.
The pre-flight ran unit (~25-35min) + vitest (~3-8min) + integration (~3-10min)
+ pack-artifact (~15min) SEQUENTIALLY via execFileSync — ~1h wall in the
v3.8.45 release (the dominant cost of Phase 0). They are independent processes
with per-process DATA_DIR isolation, so they now run in a single Promise.all
over an async runAsync() twin of run(): total drops to ~the slowest single
suite (~30min). Each still saves its per-gate log (_artifacts/release-green/)
and reports HARD independently. --quick still skips them; --with-build folds
pack-artifact into the same wave (or runs it alone under --quick).
Guard: tests/unit/validate-release-green.test.ts (13/13).