* fix(#2460): pi before_provider_request fail-opens without explicit override pi/gsd.cjs's buildBeforeProviderRequestHandler unconditionally rewrote payload.model to the built-in pi/sonnet tier default (claude-sonnet-5) via resolveTierEntry's catalog fallback. For any pi user on a non-Anthropic provider (kimi-coding, zai, openrouter, openai-codex, minimax, ...), this silently broke every request: pi's chosen model was replaced with one the active provider did not know. The fix inspects model_profile_overrides.pi[tier] explicitly BEFORE calling resolveTierEntry (which falls back to the built-in catalog and would mask the 'user did not opt in' signal). When the user has not set an override (or set it to null), the handler returns undefined — fail-open — and pi's chosen model flows through untouched. Only an explicit opt-in via model_profile_overrides.pi[tier] steers. Tests: - the ACTUALLY-REGISTERED handler fail-opens when no override configured (was: 'steers to default-tier model-catalog pi id' — encoded the bug). - new test reproducing the reporter's exact repro (model: 'k3' → undefined). - override path: explicit model_profile_overrides.pi.sonnet config steers to the user-configured model id. - defensive: explicit null override also fail-opens. Per the reporter's suggested fix #1 of #2460. * test(#2460): regen pi golden parity + install tree fixtures pi/gsd.cjs changed → pi install hash changed → regenerate the parity + install-tree fixtures via UPDATE_GOLDEN=1 + UPDATE_INSTALL_TREE=1. * fix(#2460): treat empty-string override as fail-open (M1 review) Per code-review M1 + security M1: an explicit empty-string override (`{ pi: { sonnet: "" } }`) silently bypassed the fail-open guard because the check was `=== undefined || === null` only. resolveTierEntry's falsy `if (userRaw)` then fell back to the built-in catalog and rewrote payload.model to claude-sonnet-5 — re-introducing the exact bug this PR fixes, via a degenerate config shape. Fix: widen the guard to also reject `''`. The test now exercises both null and '' in a loop, asserting fail-open for both. * fix(#2460): clear hono/@hono/node-server moderate advisories via npm override GHSA-v422-hmwv-36x6-class advisories (3 moderate) appeared during this PR's session: - @hono/node-server <2.0.5 (path traversal on Windows via encoded paths) - hono 4.3.3 - 4.12.26 (API Gateway v1 adapter drops distinct repeated request header values) - both transitively via @anthropic-ai/claude-agent-sdk -> @modelcontextprotocol /sdk@1.29.0 The npm audit fix re-resolved hono to 4.12.31 (within the existing ^4.11.4 range declared by MCP SDK), clearing the hono advisory without an override. The @hono/node-server advisory cannot be re-resolved the same way: MCP SDK pins @hono/node-server@^1.19.9, and the fix requires 2.0.5+. There is no MCP SDK release that allows @hono/node-server@2.x (latest 1.29.0 is the most recent), and bumping @anthropic-ai/claude-agent-sdk to 0.3.x does not help (its peerDependency is still @modelcontextprotocol/sdk@^1.29.0). The override (sibling to the existing 'qs' and 'body-parser' entries) is therefore the only available tool — distinct from the body-parser case in re-resolution. Verified: npm audit --omit=dev reports 0/0/0/0 advisories. Also: regenerated the pi golden-install-parity fixture (the pi/gsd.cjs change in this PR altered the pi install hash). * docs(changeset): add Fixed fragment for #2460 PR The two-otters-jog.md changeset was created earlier but lost during the cherry-pick detour to fix #2454 PR 1's npm advisory cascade. Recreating here with PR number 2499 backfilled (no placeholder cycle needed).
Changeset Fragments
This directory holds per-PR CHANGELOG fragments. Every PR with user-facing changes drops one (or more) <random-name>.md files here describing its CHANGELOG entry. Fragments are consolidated into the top-level CHANGELOG.md at release time.
Why
Two PRs that both edit the ### Fixed block of CHANGELOG.md always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh .changeset/<unique-name>.md never conflict because they don't share lines.
See #2975 for the full rationale.
Adding a fragment
node scripts/changeset/new.cjs \
--type Fixed \
--pr 1234 \
--body "fix the thing — explain the user-visible change in one sentence"
This writes .changeset/<adjective>-<noun>-<noun>.md with frontmatter and a body. Three random words → concurrent PRs don't collide.
Format
---
type: Fixed
pr: 1234
---
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.
Allowed type: values follow Keep a Changelog: Added, Changed, Deprecated, Removed, Fixed, Security.
Opting out
PRs that legitimately have no user-facing impact can add the no-changelog label. CI honors it. When unsure, add the fragment.
At release time
Promotion is automatic. The release workflow's finalize job runs:
node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD --allow-empty
This reads every fragment, groups bullets by type:, replaces ## [Unreleased] with a new ## [vX.Y.Z] - YYYY-MM-DD block, opens a fresh ## [Unreleased] above, and deletes consumed fragments. The --allow-empty flag ensures a no-change release still gets a dated heading (with a _No notable changes._ placeholder). A subsequent verify step confirms the promotion landed correctly. Maintainers do not run this by hand.
Archived fragments
.changeset/archived/ holds fragments for already-shipped releases (≤ 1.3.1), retained for provenance. Their content was hand-curated into the dated ## [1.x.y] sections of CHANGELOG.md during the #690 backfill — they were never consumed by render. All changeset tooling enumerates .changeset/ non-recursively, so archived fragments are never picked up or rendered. Do not move them back to the top level.