fix(helpers): make X-AIMock-Strict header parsing case-insensitive and whitespace-tolerant - #408
Merged
jpr5 merged 2 commits intoSep 9, 2026
Conversation
…d whitespace-tolerant Clients and proxies often normalize or user-configure header values with varying case (True/TRUE) or surrounding whitespace. The previous strict check compared literally against "true"/"false"/"1"/"0", so "True" or " true " silently fell through to the server default, causing surprising 404s/503s and reasoning suppression mismatches. Trim and lower-case the value before comparison so "True", "TRUE", " true " etc. are accepted. Unrecognised values still fall back to the server default.
commit: |
The fix shipped with no test and no changelog line, and `strict-header.test.ts` already had a `resolveStrictMode` block covering the exact-match cases — so the new behaviour had an obvious home and simply wasn't filled in. Six tests: casing on both directions, surrounding whitespace including tabs and CRLF, the two combined, and the repeated-header array form (which takes the same normalisation path but reaches it through a different branch). Two of them are negative controls, and they are the point. Trimming and lower-casing WIDEN what the comparison accepts, so the positive cases alone would also pass on an implementation that treated any non-empty string as truthy. So: `""`, `" "`, `"truthy"`, `"t"`, `"yes"`, `"on"`, `"2"`, `"-1"`, `"10"` must all still fall back to the server default, and trimming is END-only — `"tr ue"` and `"fa lse"` are different tokens, not padded ones, and must not match. Mutation-tested — reverting `resolveStrictMode` to the pre-fix literal comparison reds 4 of the 27 tests in the file; restored, 27 pass. typecheck (all three configs) exit 0; full suite 180 files / 5644 tests; eslint and prettier clean.
Merged
jpr5
added a commit
that referenced
this pull request
Sep 9, 2026
Cuts the accumulated `[Unreleased]` work. **72 non-merge commits since v1.39.0, 42 of them `feat:`/`fix:`** — minor, not patch: two additive features, no breaking changes. **Prepared, not merged.** `publish-release.yml` fires on push-to-main, so merging this publishes to npm, tags, cuts the GitHub Release, dispatches the Docker build, and posts to `#oss-alerts`. Merge when you want it live. ## What ships **Added** — `GET /__aimock/fixtures` fixture-count introspection (#407) · recorded OpenAI/OpenRouter token usage incl. OpenRouter `usage.cost` (#368) · AG-UI subagent lifecycle events + `subagentRunId` attribution (#391) **Fixed** — `X-AIMock-Strict` parsed case-insensitively with whitespace tolerated (#408) · non-websocket upgrade probes no longer 404 and the body survives on every supported Node (#410 → #418) · AG-UI drift collector no longer reports clean for an unreadable failure · AG-UI canonical parser no longer drops a field after a trailing comment · `openrouter` no longer logged as an unknown SSE provider when recording **Changed** — the AG-UI drift CI lane runs every `agui-*.drift.ts` guard rather than one hardcoded file ## Version surfaces — seven Found by grepping the repo for `1.39.0` rather than working from a checklist, because the checklist is what went wrong at v1.34.0 (four of six surfaces shipped stale). **Zero occurrences of the old string remain:** | # | file | | |---|---|---| | 1 | `package.json` | `version` | | 2 | `charts/aimock/Chart.yaml` | `appVersion` | | 3 | `.claude-plugin/plugin.json` | `version` | | 4 | `.claude-plugin/marketplace.json` | the `^` range under `plugins[0].source` | | 5 | `packages/aimock-pytest/.../_version.py` | `AIMOCK_VERSION` — the npm pin the pytest harness downloads by default | | 6 | `docs/index.html` | the version badge | | 7 | `packages/aimock-pytest/README.md` | the documented `--aimock-version` default | Two of those (6, 7) aren't in the release SOP's list but carry the version string and would have shipped stale. `packages/aimock-pytest/pyproject.toml` stays at **0.5.3** — that's the Python package's own version, on its own PyPI cadence. ## Two things I checked rather than assumed **Bumping `_version.py` in the release commit is safe.** `publish-pytest` runs `npm view @copilotkit/aimock@$AIMOCK_VERSION`, which would fail against an unpublished version — but it's `needs: [build, publish]` and gated on `needs.publish.result == 'success'`, so 1.40.0 is on npm by the time it reads the pin. No chicken-and-egg. **`package.json.description` is deliberately not hand-synced.** It diverges from the README subtitle in git, which looks like the drift the SOP warns about. It isn't: `publish-release.yml` rewrites it from the README subtitle *in the runner* before `npm publish`, without committing. Editing it here would fight the workflow. ## README Gains a **Recorded token usage and cost** bullet. The feature list enumerates record/replay capabilities individually (timing-aware replay, multi-turn, ordered blocks), so a user-visible one landing without an entry is a real omission. The new control-API route needs no README line — the README documents no `/__aimock/*` routes at all; that lives in `docs/control-api`. ## Verification `typecheck` (all three configs) exit 0 · `build` exit 0 · release/publish-pin/drift-script workflow tests 44 passing · prettier clean · commit body wrapped ≤100 cols for commitlint. Full-suite note: five tests failed on a first run and all five passed in isolation — `cli.test.ts` SIGTERM, `multimedia.test.ts` transcription frame scheduling, `proxy-buffer-cap.test.ts`, and both `publish-pin-workflow.test.ts` real-pip cases. All wall-clock or network-bound, and none reachable from a version-string change. CI is the arbiter. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Y445N6QQBeAdiLcEvEpqGe
pull Bot
pushed a commit
to TheTechOddBug/aimock
that referenced
this pull request
Sep 9, 2026
Cuts the accumulated Unreleased work: 72 non-merge commits since v1.39.0, 42 of them feat/fix. Minor, not patch — two additive features, no breaking changes. ### Added - GET /__aimock/fixtures — read-only fixture-count introspection (CopilotKit#407) - Recorded OpenAI/OpenRouter token usage, including OpenRouter usage.cost (CopilotKit#368) - AG-UI subagent lifecycle events + subagentRunId attribution (CopilotKit#391) ### Changed - The AG-UI drift CI lane runs every agui-*.drift.ts guard, not one file ### Fixed - X-AIMock-Strict is parsed case-insensitively, whitespace tolerated (CopilotKit#408) - Non-websocket upgrade probes no longer 404, and the body survives on every supported Node (CopilotKit#410, CopilotKit#418) - The AG-UI drift collector no longer reports clean for an unreadable failure - The AG-UI drift canonical parser no longer drops a field after a trailing comment - openrouter is no longer logged as an unknown SSE provider when recording Version surfaces bumped — seven, verified by grepping the repo for the old string rather than working from a list (zero occurrences remain): package.json, charts/aimock/Chart.yaml appVersion, .claude-plugin/plugin.json, .claude-plugin/marketplace.json (the `^` range under plugins[0].source), packages/aimock-pytest/src/aimock_pytest/_version.py (the npm pin the pytest harness downloads by default), docs/index.html's version badge, and packages/aimock-pytest/README.md's documented default. packages/aimock-pytest/pyproject.toml stays at 0.5.3 — that is the Python package's own version, released on its own PyPI cadence. README gains a Recorded-token-usage-and-cost bullet: the feature list enumerates record/replay capabilities individually, so a user-visible one landing without an entry there is a real omission. The new control-API route needs no README line — the README documents no /__aimock/* routes at all; it is covered in docs/control-api. package.json's description is deliberately NOT hand-synced: publish-release.yml rewrites it from the README subtitle in the runner before npm publish, without committing, which is why git and the npm page differ by design.
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.
Clients, proxies, and shell snippets often emit
X-AIMock-Strict: True,TRUE, ortruewith surrounding whitespace. The previousresolveStrictModecompared literally against"true"/"false"/"1"/"0", so those variants silently fell back to the server default.On a
--strict falseserver that meantX-AIMock-Strict: Truestayed at404instead of503; on a--strict trueserverX-AIMock-Strict: Falsestayed strict. The same header also gates reasoning suppression, so the mismatch could surface as flaky reasoning assertions.Change:
"true"/"TRUE"/" True " / "1" / " 1 "→ strict on"false"/"FALSE"/" false " / "0" / " 0 "→ strict offserverDefaultVerified:
pnpm run format:check/lint/typecheckcleancreateServer:openrouter-video.test.tsstrict-header tests (lower-case) still pass (123 passed)