Skip to content

Canonical provider classification, forward-client model invariant, and the environment model on the public surface - #3936

Merged
chelojimenez merged 13 commits into
mainfrom
claude/environment-model-matrix-b4gyef
Aug 13, 2026
Merged

Canonical provider classification, forward-client model invariant, and the environment model on the public surface#3936
chelojimenez merged 13 commits into
mainfrom
claude/environment-model-matrix-b4gyef

Conversation

@chelojimenez

@chelojimenez chelojimenez commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Pairs with MCPJam/mcpjam-backend#948. Deploy the backend first — the capability probe and the new resolve fields come from there.

One provider classifier

"Given only this string, which provider serves it?" was answered slightly differently in four places, and the divergences were real bugs: meta-llama/... came back as meta-llama from one and meta from another; mistralai/... fell through to Ollama; a bare unknown id became openrouter in chat and ollama in the runner.

shared/model-provider.ts is now the source of truth: blank → null (never a guess — this is the guard that stops an unpinned host's persisted "" becoming a plausible Ollama model), custom:<slug> → custom, a recognized <prefix>/ including the new mistralai → mistral alias, a bare Bedrock shape, everything else → ollama. buildSyntheticModelDefinition, the chat-session locked-model fallback, and the public eval API's providerForModelId all delegate to it.

Two intentional behavior changes, both correcting a divergence rather than introducing one: a bare unrecognized id no longer becomes openrouter in chat sessions, and mistralai/... no longer becomes ollama.

Parity vectors live in shared/__tests__/model-provider-fixtures.ts and are mirrored verbatim into the backend, ratcheted by its check:mirrors.

Forward-client model invariant

An environment resolves its model from its own override, else its client's, else nowhere — and "nowhere" is a hard launch refusal. So a client minted without a model is a client that cannot back a headless environment, and the failure would surface at launch rather than at creation.

POST /v1/.../hosts refines config.modelId as a required non-empty string (templates are guarded, never substituted — each keeps its own model). The Behavior tab blocks Save on an edit that clears a pinned model, while a legacy modelless client stays editable for unrelated changes: those rows predate the invariant, are deliberately not auto-backfilled, and stranding every rename behind a model choice would be the wrong trade.

Public surface

  • v1 environments: modelId on create (non-empty) and PATCH (tri-state, null clears); the resolve DTO gains modelId, effectiveModelId, modelSource. ENV_MODEL_REQUIRED maps to 409 with a branchable details.reason = "environment_model_required".
  • GET .../environments/capabilities proxies the backend probe and answers false — never an error — when the backend cannot answer, because absence is the signal an old deployment gives. Registered above /:environmentId so the literal segment is not read as an id. Documented in openapi.json.
  • SDK: model fields on the environment, both bodies and the resolved type; PlatformEnvironmentCapabilities; getEnvironmentCapabilities; a get_project_environment_capabilities operation, excluded from the agent registry (a compatibility probe is not an action).
  • CLI: environments create --model, environments update --model / --clear-model (mutually exclusive), both preflighting the capability only when model input was supplied — an ordinary create shouldn't pay for a round-trip to guard a field it never sends. JSON input keeps accepting "modelId": null.

The backend's own @mcpjam/sdk@^2.0.1 dependency is deliberately not bumped.

Journey launch

402 maps to BILLING_LIMIT_REACHED. launchFailureMessage unwraps structured bodies (v1 envelope, Convex error data), keeps a plain sentence the backend wrote for a human, and refuses HTML, multi-line, or oversized bodies — bodyText is never passed through unexamined. The swarm create flow stops scheduling on the first 402 (every sibling would be rejected identically) and reports one billing message for the wave; in-flight requests are allowed to settle rather than aborted, because a launch POST may already have created a durable run.

The launchJourney option type now declares the environmentIds its call site was already passing — a conditional spread had been defeating excess-property checking, leaving the per-run fan-out one rename away from being silently dropped.

Eval history

aggregateSuite labels a case from the iteration's snapshot rather than the case's current definition, so editing a case no longer relabels finished runs with a model they never used. The bare-rerun precheck no longer rejects model-less prompt cases on an environment-backed run — the backend projects and precreates them, so the premise of that check no longer holds there.

Verification

Workspace npm run typecheck ✅ · SDK tests 4254 passed ✅ · CLI tests 564 passed ✅ · Inspector --project server 5250 passed ✅ · --project shared 577 passed ✅ · npm run build:inspector

--project client: 8617 passed, 2 failed — both pre-existing on main (ChatboxShareSection "issues the link again once the environment resolves", SwarmsTab.perClientEnvLaunch "refuses the whole launch when one of the two environments is gone"), verified by re-running them stashed.

Not in this PR

The eval/swarm composer two-mode UI (client_defaults / compare, the client × model cross-product, cell-aware collapse guards, draft v2) and the named-environment editor's model controls are not implemented — see the PR discussion for the handoff. Everything they will need is in place and capability-gated, and no user-facing entry point to compare mode ships here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy


Generated by Claude Code


Note

High Risk
Touches public environment/host contracts, model resolution used at launch, and billing/error handling for swarm waves. Deploy backend first — capability probe and resolve fields depend on it.

Overview
Environments can now pin a model override (or inherit the host's) across the public API, SDK, and CLI. Resolve responses add effectiveModelId / modelSource, and ENV_MODEL_REQUIRED becomes a branchable 409. A new capabilities probe lets clients detect support under version skew; the CLI preflights it only when --model / --clear-model / modelId is actually supplied.

Provider classification is centralized in shared/model-provider.ts so chat, synthetic models, and the eval API agree (including mistralai → mistral and bare-id → ollama). New hosts must pin a model; clearing a previously pinned model blocks Save, while legacy modelless hosts stay editable.

Also hardens swarm launch: maps 402 to billing, stops scheduling the rest of a wave on credit limit, and sanitizes failure messages. Eval history labels cases from the iteration snapshot so later edits don't rewrite finished runs.

Reviewed by Cursor Bugbot for commit 145b7cb. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Unifies provider classification, enforces a pinned-model host invariant across create/patch/duplicate, and exposes environment model overrides with a capability probe on API, SDK, and CLI. Behavior changes include rejecting blank model ids; mistralai/... now maps to mistral; chat no longer guesses openrouter; and known vendor prefixes no longer default to ollama.

  • API

    • Environments accept modelId on create and PATCH (null clears). Resolve adds modelId, effectiveModelId, and modelSource. List/Get emit only the stored override.
    • GET /projects/{projectId}/environments/capabilities reports support; returns false only on older deployments. Auth/outage errors propagate.
    • ENV_MODEL_REQUIRED translates to 409 with details.reason = "environment_model_required" from the resolve route and the eval recorder.
    • Hosts: require a trimmed, non-empty config.modelId on create; PATCH refuses clearing a previously pinned model (legacy modelless hosts remain editable); duplicate requires a pinned model; templates are guarded, not substituted.
  • SDK (@mcpjam/sdk)

    • Adds environment modelId and resolved-model fields; defines PlatformEnvironmentCapabilities; adds getEnvironmentCapabilities; exports EnvironmentCapabilitiesInput/Result.
    • create_host schema enforces XOR of template vs config and requires a non-empty model when using config.
  • CLI

    • environments create --model, environments update --model|--clear-model (mutually exclusive). Capability preflight runs only when a model arg is provided and against the target project.
  • Provider classification

    • Canonical classifier trims ids; maps mistralai/... → mistral (also meta → meta-llama, xai → x-ai aliases); bare unknowns → ollama; guards Object.prototype traps. Chat, evals, and synthetic models delegate to it. Blank ids are rejected.
    • Vendor attribution consults the hosted catalog for every id. Qualified ids with known vendors (cohere/, nvidia/, amazon/, bytedance/, stepfun/, etc.) attribute correctly; unknown vendors keep their own prefix instead of defaulting to ollama.
  • Evals and cost estimates

    • Reject blank/whitespace-only model ids. Provider derivation uses the classifier. Environment-backed runs produce one capability entry per case; cost estimates include environmentIds. Mixed-model detection keys all iterations. Stored ids are trimmed.
    • Model attribution: hosted iterations persist the resolved modelId; local iterations persist modelId only when the case needed a model (including failure paths).
  • Launch handling

    • Backend 402 → BILLING_LIMIT_REACHED; stop scheduling the remaining launches in the wave. The billing message has its own slot and does not get replaced by unrelated earlier failures.
    • Failure details prefer the backend’s details.code over the envelope transport code; messages unwrap structured bodies, are single-line, no markup, size-bounded.
  • Rollout

    • Deploy the backend first (capabilities route and resolve fields).
    • Ensure new hosts/templates pin a non-empty model; block clearing a pinned model; duplicate enforces the same.
    • Probe environment capabilities before sending modelId; older deployments answer false.
    • Note classifier and attribution changes: mistralai/... maps to mistral; qualified hosted vendors no longer attribute to ollama; unknown vendors keep their prefix.

Written for commit 6791378. Summary will update on new commits.

Review in cubic

claude added 2 commits August 12, 2026 08:04
…ient model invariant

Extract the provider-classification rules that four call sites had each
re-derived slightly differently into one pure `shared/model-provider.ts`, and
hold new hosts to a non-empty model so an environment can always resolve one.

- `classifyModelIdProvider` is the single source of truth: blank -> null,
  `custom:<slug>` -> custom, prefix map (now including the `mistralai -> mistral`
  alias alongside `meta-llama -> meta` and `x-ai -> xai`), bare Bedrock shapes,
  everything else -> ollama.
- `buildSyntheticModelDefinition`, the chat-session locked-model fallback, and
  the public eval API's `providerForModelId` all delegate to it. Two intentional
  behavior changes: a bare unrecognized id no longer becomes `openrouter` in
  chat sessions, and `mistralai/...` no longer becomes `ollama`.
- Parity fixtures live in `shared/__tests__/model-provider-fixtures.ts`, copied
  into the backend mirror's test so the two implementations cannot drift.
- `POST /v1/.../hosts` now refines `config.modelId` as a required non-empty
  string (templates are guarded, never substituted), and the Behavior tab
  blocks Save on an edit that CLEARS a pinned model while leaving legacy
  modelless hosts editable for unrelated changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy
…lity probe

Threads `modelId` through the public surface, and gives clients a way to ask
whether the deployment in front of them accepts it — the SDK and CLI ship
independently of the platform, and an unknown field is a hard validator error
there, not a silently ignored one.

- v1 environments: `modelId` on create (non-empty) and PATCH (tri-state, `null`
  clears); the resolve DTO gains `modelId`, `effectiveModelId` and
  `modelSource`. `ENV_MODEL_REQUIRED` maps to 409 with a branchable
  `details.reason = "environment_model_required"`.
- `GET .../environments/capabilities` proxies the backend probe and answers
  `false` — never an error — when the backend cannot answer, because ABSENCE is
  the signal an old deployment gives. Registered above `/:environmentId` so the
  literal segment is not read as an id. Documented in openapi.json.
- SDK: `modelId` on the environment type and both bodies, model fields on the
  resolved type, `PlatformEnvironmentCapabilities`,
  `getEnvironmentCapabilities`, and a `get_project_environment_capabilities`
  operation (excluded from the agent registry — a compatibility probe is not an
  action).
- CLI: `environments create --model`, `environments update --model` /
  `--clear-model` (mutually exclusive), both preflighting the capability ONLY
  when model input was supplied. JSON input keeps accepting `"modelId": null`.
- Journey launch: 402 maps to `BILLING_LIMIT_REACHED`; the failure message
  unwraps structured bodies, keeps a plain backend sentence, and refuses HTML,
  multi-line, or oversized bodies. The swarm create flow stops scheduling on
  the first 402, reports one billing message for the wave, and the
  `launchJourney` option type finally declares the `environmentIds` its call
  site was already passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy
@mintlify

mintlify Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
mcpjam 🟢 Ready View Preview Aug 12, 2026, 10:09 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels Aug 12, 2026
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_222431d2-7e04-448c-888d-10c20d92c289)

@chelojimenez

chelojimenez commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Internal preview

Preview URL: https://mcp-inspector-pr-3936.up.railway.app
Deployed commit: 723c491
PR head commit: 6791378
Backend target: staging fallback.
Health: ✅ Convex reachable
Access is employee-only in non-production environments.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 29 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread mcpjam-inspector/server/routes/v1/environments.ts Outdated
Comment thread mcpjam-inspector/server/routes/v1/evals.ts Outdated
Comment thread mcpjam-inspector/shared/model-provider.ts Outdated
Comment thread mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts Outdated
Comment thread mcpjam-inspector/server/routes/v1/hosts.ts
Comment thread mcpjam-inspector/client/src/components/evals/helpers.ts Outdated
Comment thread cli/src/commands/environments.ts Outdated
Comment thread mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts Outdated
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds environment-level model overrides with capability discovery, SDK support, CLI flags, validation, and effective-model metadata. It centralizes model-provider classification across inspector services. Host validation now blocks clearing saved models. Journey launches stop scheduling after billing-limit errors and display dedicated partial-launch warnings. Evaluation helpers use iteration snapshots, environment identities, and persisted model metadata.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (3)
mcpjam-inspector/server/routes/v1/agent-op-registry.ts (1)

645-646: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for both operation-surface exclusions.

The new exclusions are correct, but they need tests that prevent future registry drift.

  • mcpjam-inspector/server/routes/v1/agent-op-registry.ts#L645-L646: assert that get_project_environment_capabilities is excluded from direct and gated agent operations.
  • mcpjam-inspector/server/utils/built-in-tools/mcpjam.ts#L168-L169: assert that the operation is excluded from the workspace tool surface.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/server/routes/v1/agent-op-registry.ts` around lines 645 -
646, Add regression tests covering both operation-surface exclusions: verify
get_project_environment_capabilities is absent from direct and gated agent
operations in mcpjam-inspector/server/routes/v1/agent-op-registry.ts at lines
645-646, and absent from the workspace tool surface in
mcpjam-inspector/server/utils/built-in-tools/mcpjam.ts at lines 168-169. Anchor
the assertions to the relevant registry and workspace-tool construction symbols.

Source: Coding guidelines

mcpjam-inspector/server/routes/shared/evals.ts (1)

1605-1620: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Add backend-backed regression coverage for the environment rerun bypass.

Cover a model-less prompt with an effective environment model, a missing environment model returning ENV_MODEL_REQUIRED, and a bare rerun without environmentId still failing assertBareRerunCasesRunnable. Current tests cover the local guard and environment-ID forwarding, but not the complete projection and iteration-recording path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/server/routes/shared/evals.ts` around lines 1605 - 1620, Add
backend-backed regression tests for the environment rerun flow around
assertBareRerunCasesRunnable and startTestSuiteRun: verify a model-less prompt
uses the environment’s effective model and records iterations, an environment
without a resolvable model fails with ENV_MODEL_REQUIRED, and a rerun without
environmentId still invokes the bare-rerun guard and fails. Reuse the existing
environment-ID forwarding and local-guard test setup while exercising the
complete projection and iteration-recording path.

Source: Coding guidelines

mcpjam-inspector/client/src/components/evals/helpers.ts (1)

499-516: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make aggregateSuite scope explicit.

activeIterations includes iterations from multiple suite runs, but byCaseMap combines their counts and keeps metadata from the first row. A case rerun with another model can therefore display the wrong model. Either aggregate one run at a time or preserve run/model identity in SuiteAggregate.byCase.

Add tests for differing snapshots, mixed legacy and snapshot rows, and null or empty snapshot fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/client/src/components/evals/helpers.ts` around lines 499 -
516, Update aggregateSuite and SuiteAggregate.byCase so aggregation is scoped to
an individual suite run, preventing activeIterations from combining counts or
retaining metadata from another run/model. Preserve snapshot precedence with
fallback for legacy rows, including correct handling of differing snapshots,
mixed legacy and snapshot rows, and null or empty snapshot fields. Add focused
tests covering these cases.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/commands/environments.ts`:
- Around line 119-132: Update assertModelOverridesSupported and the analogous
capability probes near the other referenced locations to pass the effective
target project selector to getEnvironmentCapabilitiesOperation.execute instead
of an empty input. Resolve it from the --project option, falling back to the
JSON project field when present, so capability checks and subsequent commands
use the same project.

In `@docs/reference/openapi.json`:
- Around line 8886-8890: Update both request modelId schema properties to reject
whitespace-only strings by adding the pattern .*\\S.* alongside minLength,
preserving the existing type, description, and minLength declarations.

In `@mcpjam-inspector/client/src/components/swarms/new-swarm-create-flow.tsx`:
- Around line 257-289: Add tests for runWithConcurrency covering a worker
returning "stop", ensuring in-flight workers settle while no additional items
are scheduled. Add launch-flow tests for partial billing blocking and ordinary
partial failure, asserting the billing path displays exactly one billing
warning; include relevant happy-path, validation, error, and empty/null edge
cases where applicable.

In `@mcpjam-inspector/server/routes/v1/__tests__/environments.test.ts`:
- Around line 520-538: Extend the resolve endpoint tests around “carries
effectiveModelId and modelSource through resolve” with a host-derived case: mock
a resolved row without a stored modelId, set effectiveModelId to the
host-derived value and modelSource to “host,” then assert the response preserves
those values and omits or leaves modelId unset as appropriate.

In `@mcpjam-inspector/server/routes/v1/environments.ts`:
- Around line 460-464: Update the capability-fetch error handling around the
Convex query in environments.ts so only the recognized missing-query deploy-skew
error falls back to unsupported capabilities; rethrow or normally translate
authorization, not-found, and other service errors. In
mcpjam-inspector/server/routes/v1/__tests__/environments.test.ts lines 587-600,
preserve the missing-query fallback test and add coverage confirming project
access failures do not return HTTP 200 with unsupported capabilities.

In `@mcpjam-inspector/server/routes/v1/evals.ts`:
- Around line 1311-1318: Update normalizeCreateTestsToRunTests to derive an
omitted provider through the canonical providerForModelId(model) resolver
instead of splitting the model string prefix, preserving correct alias and
custom-ID handling. Add route tests covering suite creation with omitted
providers for both aliased models and custom model IDs.

In `@mcpjam-inspector/server/routes/v1/hosts.ts`:
- Around line 112-123: Add server route tests for the host-creation handler
covering both template and explicit-config inputs: valid requests,
missing/null/empty/whitespace-only config.modelId, empty config, and templates
resolving without a model. Assert successful responses for valid requests and
the expected validation response for every rejected case, including the
WebRouteError raised by hostConfigPinsAModel.

In
`@mcpjam-inspector/server/services/sessionSimulation/__tests__/launch-journey-run.test.ts`:
- Around line 183-193: Extend the parameterized cases in the
launchFailureMessage coverage within launch-journey-run.test.ts to include an
empty body and malformed JSON such as "{". Verify both rejected SwarmAgentError
inputs preserve status 402 and produce the safe credit-limit fallback message
through launchJourneyRun.

In `@mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts`:
- Around line 120-144: Update the structured-message handling in the launch
journey reason parser to validate extracted envelope and ConvexError message
values with the same length, newline, and markup policy used by looksLikeProse
before returning them. Preserve the fallback behavior when validation fails, and
add regression coverage for structured multiline, markup-containing, and
oversized messages.

In `@sdk/src/platform/index.ts`:
- Line 66: Update the public type exports in the platform index to include
EnvironmentCapabilitiesInput and EnvironmentCapabilitiesResult alongside
PlatformEnvironmentCapabilities and getEnvironmentCapabilitiesOperation.
Re-export both named types from ./operations.js so consumers can access the
complete capability operation API.

---

Nitpick comments:
In `@mcpjam-inspector/client/src/components/evals/helpers.ts`:
- Around line 499-516: Update aggregateSuite and SuiteAggregate.byCase so
aggregation is scoped to an individual suite run, preventing activeIterations
from combining counts or retaining metadata from another run/model. Preserve
snapshot precedence with fallback for legacy rows, including correct handling of
differing snapshots, mixed legacy and snapshot rows, and null or empty snapshot
fields. Add focused tests covering these cases.

In `@mcpjam-inspector/server/routes/shared/evals.ts`:
- Around line 1605-1620: Add backend-backed regression tests for the environment
rerun flow around assertBareRerunCasesRunnable and startTestSuiteRun: verify a
model-less prompt uses the environment’s effective model and records iterations,
an environment without a resolvable model fails with ENV_MODEL_REQUIRED, and a
rerun without environmentId still invokes the bare-rerun guard and fails. Reuse
the existing environment-ID forwarding and local-guard test setup while
exercising the complete projection and iteration-recording path.

In `@mcpjam-inspector/server/routes/v1/agent-op-registry.ts`:
- Around line 645-646: Add regression tests covering both operation-surface
exclusions: verify get_project_environment_capabilities is absent from direct
and gated agent operations in
mcpjam-inspector/server/routes/v1/agent-op-registry.ts at lines 645-646, and
absent from the workspace tool surface in
mcpjam-inspector/server/utils/built-in-tools/mcpjam.ts at lines 168-169. Anchor
the assertions to the relevant registry and workspace-tool construction symbols.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ed76f0ef-4bb4-442a-b833-0cdab418dcca

📥 Commits

Reviewing files that changed from the base of the PR and between 8cedc9e and 145b7cb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • cli/src/commands/environments.ts
  • cli/src/lib/op-bindings.ts
  • docs/reference/openapi.json
  • mcpjam-inspector/client/src/components/evals/helpers.ts
  • mcpjam-inspector/client/src/components/hosts/redesigned/HostBuilderViewRedesigned.tsx
  • mcpjam-inspector/client/src/components/hosts/redesigned/focus/__tests__/useHostDraftValidation.model.test.ts
  • mcpjam-inspector/client/src/components/hosts/redesigned/focus/useHostDraftValidation.ts
  • mcpjam-inspector/client/src/components/swarms/new-swarm-create-flow.tsx
  • mcpjam-inspector/client/src/hooks/use-chat-session.ts
  • mcpjam-inspector/server/routes/shared/evals.ts
  • mcpjam-inspector/server/routes/v1/__tests__/environments.test.ts
  • mcpjam-inspector/server/routes/v1/__tests__/sdk-coverage.test.ts
  • mcpjam-inspector/server/routes/v1/agent-op-registry.ts
  • mcpjam-inspector/server/routes/v1/environments.ts
  • mcpjam-inspector/server/routes/v1/evals.ts
  • mcpjam-inspector/server/routes/v1/hosts.ts
  • mcpjam-inspector/server/services/sessionSimulation/__tests__/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
  • mcpjam-inspector/server/utils/built-in-tools/mcpjam.ts
  • mcpjam-inspector/server/utils/org-model-config.ts
  • mcpjam-inspector/shared/__tests__/model-provider-fixtures.ts
  • mcpjam-inspector/shared/__tests__/model-provider.test.ts
  • mcpjam-inspector/shared/model-provider.ts
  • sdk/src/platform/client.ts
  • sdk/src/platform/index.ts
  • sdk/src/platform/operations.ts
  • sdk/src/platform/types.ts
  • sdk/tests/platform/operations.test.ts

Comment thread cli/src/commands/environments.ts Outdated
Comment thread docs/reference/openapi.json
Comment on lines +257 to +289
/**
* Run `worker` over `items`, at most `limit` at a time, and STOP SCHEDULING
* once `worker` reports the wave is doomed.
*
* The stop signal exists for one failure: an organization that hits its credit
* limit. Every remaining launch in the wave will be rejected for exactly the
* same reason, so firing them costs a round-trip each and produces N identical
* banners. A worker returns `"stop"` and no further item is picked up.
*
* Requests ALREADY in flight are not cancelled here — a launch is a POST that
* may have already created a durable run row, and aborting the client half
* would leave one running with nobody watching. They are allowed to settle;
* the caller deduplicates their errors so one billing message is shown, not
* `limit` of them.
*/
async function runWithConcurrency<T>(
items: T[],
limit: number,
worker: (item: T) => Promise<void>
worker: (item: T) => Promise<void | "stop">
): Promise<void> {
let cursor = 0;
let stopped = false;
const runners = Array.from(
{ length: Math.min(limit, items.length) },
async () => {
while (cursor < items.length) {
if (stopped) return;
const index = cursor;
cursor += 1;
await worker(items[index]);
if ((await worker(items[index])) === "stop") {
stopped = true;
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add tests for terminal billing scheduling.

Add client coverage for a worker that returns "stop", including already-running workers settling without scheduling further items. Add launch-flow coverage for a partial billing block and for an ordinary partial failure. Verify that the billing path shows one billing warning.

As per coding guidelines: “All changes should include tests, covering happy paths, validation errors, error handling, and edge cases such as null and empty values.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/client/src/components/swarms/new-swarm-create-flow.tsx`
around lines 257 - 289, Add tests for runWithConcurrency covering a worker
returning "stop", ensuring in-flight workers settle while no additional items
are scheduled. Add launch-flow tests for partial billing blocking and ordinary
partial failure, asserting the billing path displays exactly one billing
warning; include relevant happy-path, validation, error, and empty/null edge
cases where applicable.

Source: Coding guidelines

Comment thread mcpjam-inspector/server/routes/v1/__tests__/environments.test.ts
Comment thread mcpjam-inspector/server/routes/v1/environments.ts Outdated
Comment thread mcpjam-inspector/server/routes/v1/evals.ts Outdated
Comment on lines +112 to +123
// The forward-client invariant applies to the template branch too. Templates
// carry their OWN model (each is tuned to the client it emulates), so this is
// a guard, never a substitution — a catalog entry that lost its model is a
// catalog bug, and minting a modelless host from it would surface as an
// `ENV_MODEL_REQUIRED` launch refusal much later.
if (!hostConfigPinsAModel(input)) {
throw new WebRouteError(
400,
ErrorCode.VALIDATION_ERROR,
`Host template "${templateId}" does not pin a model; pass an explicit \`config\` instead.`
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add route tests for the new host-creation validation.

No included server test covers these public validation branches. Add tests for valid template and explicit-config requests, missing, null, empty, and whitespace-only config.modelId, an empty config, both input branches, and a template that resolves without a model. Assert the validation response for each rejected request.

As per coding guidelines, “All changes should include tests, covering happy paths, validation errors, error handling, and edge cases such as null and empty values.”

Also applies to: 194-247

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/server/routes/v1/hosts.ts` around lines 112 - 123, Add
server route tests for the host-creation handler covering both template and
explicit-config inputs: valid requests, missing/null/empty/whitespace-only
config.modelId, empty config, and templates resolving without a model. Assert
successful responses for valid requests and the expected validation response for
every rejected case, including the WebRouteError raised by hostConfigPinsAModel.

Source: Coding guidelines

Comment thread mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts Outdated
Comment thread sdk/src/platform/index.ts
CI caught a third operation registry I had missed; the rest are review findings
from cubic-dev-ai and CodeRabbit that turned out to be real.

- `mcp/src/tools/platformTools.ts` enforces its own catalog partition, so the
  new `get_project_environment_capabilities` operation had to be classified
  there too. This was the only genuine CI failure.
- `classifyModelIdProvider` now looks the prefix up with `hasOwnProperty`. A
  bare index made `constructor/x` and `toString/x` return a FUNCTION off
  `Object.prototype` — truthy, so it was handed back as the provider. Fixture
  vectors added on both sides of the mirror.
- `providerForModelId` trims before the catalog lookup, so a padded bare
  catalog id resolves to its real vendor instead of the Ollama catch-all; and
  `normalizeCreateTestsToRunTests` uses it instead of `model.split("/")[0]`,
  which derived `meta-llama`/`mistralai` as providers and refused every
  `custom:<slug>:<model>` id.
- `POST /v1/.../hosts` trims `config.modelId` rather than persisting a padded
  value that downstream comparison would treat as an unknown model.
- The capabilities route swallows ONLY the missing-function (deploy-skew)
  error. Collapsing an authorization failure or an outage into a 200 with both
  capabilities false told the caller to upgrade a platform that was already
  current instead of fixing their access.
- `launchFailureMessage` applies one policy — bounded, single-line, no markup —
  to a message unwrapped from a structured body as well as to a plain one; an
  envelope is no more trustworthy about what it carries.
- The CLI capability preflight probes the TARGET project (`--project`, else the
  JSON body's `project`), not the caller's default, which could have blocked a
  valid write or passed one the target rejects.
- SDK exports `EnvironmentCapabilitiesInput` / `EnvironmentCapabilitiesResult`;
  the OpenAPI `modelId` request properties reject whitespace-only strings.

Tests added for the host model invariant (missing/null/empty/whitespace/
non-string, padded, modelless template, and the XOR message), the capability
route's old-deployment vs access-failure split, host-derived resolve, and the
structured/empty/malformed launch-failure bodies.

`aggregateSuite`'s one-row-per-case folding is documented rather than
restructured: it is pre-existing (the live case's `models[0]` was equally
single-valued) and narrowing it means re-keying `byCase`, which changes the
type every caller renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_4a62f15c-be1f-446e-a1e9-c6b3896c1587)

Copy link
Copy Markdown
Contributor Author

Pushed 9bf0ee8 (and 4bf89e5 on the backend). CI and review findings both addressed.

CI

Run Tests was a real miss and is fixed. mcp/src/tools/platformTools.ts enforces its own catalog partition — a third operation registry beyond the agent registry and the built-in tool surface I had already updated — so the new get_project_environment_capabilities operation had to be classified there too.

Inspector Tests 1/4 and 4/4 are not from this branch.

  • 1/4 is ChatboxShareSection › "issues the link again once the environment resolves" and SwarmsTab.perClientEnvLaunch › "refuses the whole launch when one of the two environments is gone". Both fail on main — verified by stashing this branch and re-running each.
  • 4/4 is ChatboxChatPage.test.tsx:1027, which passes locally both in isolation and across the whole --shard=4/4 (306 files, 3453 tests). CI-only, so I'm reading it as a flake rather than something to chase; it will re-run on this push.

Review findings taken

A real bug, thank you — the prefix lookup. MODEL_ID_PREFIX_TO_PROVIDER is an ordinary object, so constructor/x or toString/x read a function off Object.prototype, and the truthiness check handed it back as the provider. Now hasOwnProperty, in both repos, with fixture vectors for constructor / toString / __proto__ / hasOwnProperty on both sides of the mirror.

Also fixed:

  • providerForModelId trims before the catalog lookup, and normalizeCreateTestsToRunTests now uses it instead of model.split("/")[0] — that copy derived meta-llama and mistralai as providers and refused every custom:<slug>:<model> id, which is exactly the divergence this PR exists to remove. Good catch that I'd left one behind.
  • POST /v1/.../hosts trims config.modelId (matching the environment contract's normalizeModelId) rather than persisting a padded value.
  • The capabilities route swallows only the missing-function error. You're right that collapsing an authorization failure into a 200 with both capabilities false tells the caller to upgrade a platform that is already current instead of fixing their access.
  • launchFailureMessage applies one policy — bounded, single-line, no markup — to a message unwrapped from a structured body as well as to a plain one, via a shared showableReason. The catch comment is corrected too: a {-prefixed body that fails to parse is a broken envelope and is deliberately not re-tested as prose.
  • The CLI preflight probes the target project (--project, else the JSON body's project). Probing the default project could have blocked a valid --model write or passed one the target rejects — the exact failure the preflight exists to prevent.
  • SDK exports EnvironmentCapabilitiesInput / EnvironmentCapabilitiesResult; OpenAPI modelId request properties reject whitespace-only strings.

New tests: the host model invariant (missing / null / empty / whitespace-only / non-string, the padded-trim case, a modelless template, and that config: {} still reports the XOR problem rather than the model), the capability route's old-deployment vs access-failure split, a host-derived resolve, and empty / malformed / structured-oversized / structured-markup launch-failure bodies.

Two I did not take

aggregateSuite's one-row-per-case folding. The observation is correct — when a caller passes iterations from several runs, the label comes from whichever iteration is seen first while the counts cover all of them. But that is pre-existing rather than introduced here: the old code read the live case's models[0], which is equally single-valued, so this change strictly improves accuracy without changing the shape. Fixing it properly means re-keying byCase on run/model, which changes a type every caller renders — worth doing, but not inside a model-plumbing PR. I've documented the scope in the code instead.

Forwarding structured billing details on 402. Reasonable, and genuinely useful for upgrade/reset messaging, but it's a new capability rather than a correction, and the fan-out currently only needs the code to decide to stop scheduling. Happy to add it if you'd like it in this PR.

Verification

Backend lint:ci (0 errors) / typecheck / test:once ✅ · check:mirrors --upstream 10/10 in hash and diff mode ✅ · workspace typecheck ✅ · SDK 4254 ✅ · CLI 564 ✅ · @mcpjam/mcp 47 ✅ · Inspector --project server 5270 ✅ · --project shared 581 ✅


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

MCP worker preview

Preview worker mcpjam-mcp-pr-3936 deleted — the preview URL no longer resolves.
Merged changes are live on mcpjam-mcp-staging via deploy-mcp-staging.yml.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcpjam-inspector/server/routes/v1/__tests__/hosts.test.ts`:
- Around line 422-433: Update the padded-model test case in the POST host
request flow to assert that the response status is 201 before validating
createdHostInput(). Keep the existing trimmed modelId assertion unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 565826d9-2527-4f2d-b80b-3a7886b5c22f

📥 Commits

Reviewing files that changed from the base of the PR and between 145b7cb and 9bf0ee8.

📒 Files selected for processing (14)
  • cli/src/commands/environments.ts
  • docs/reference/openapi.json
  • mcp/src/tools/platformTools.ts
  • mcpjam-inspector/client/src/components/evals/helpers.ts
  • mcpjam-inspector/server/routes/v1/__tests__/environments.test.ts
  • mcpjam-inspector/server/routes/v1/__tests__/hosts.test.ts
  • mcpjam-inspector/server/routes/v1/environments.ts
  • mcpjam-inspector/server/routes/v1/evals.ts
  • mcpjam-inspector/server/routes/v1/hosts.ts
  • mcpjam-inspector/server/services/sessionSimulation/__tests__/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
  • mcpjam-inspector/shared/__tests__/model-provider-fixtures.ts
  • mcpjam-inspector/shared/model-provider.ts
  • sdk/src/platform/index.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • mcpjam-inspector/server/routes/v1/hosts.ts
  • sdk/src/platform/index.ts
  • mcpjam-inspector/shared/tests/model-provider-fixtures.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
  • mcpjam-inspector/server/services/sessionSimulation/tests/launch-journey-run.test.ts
  • mcpjam-inspector/server/routes/v1/evals.ts
  • mcpjam-inspector/server/routes/v1/tests/environments.test.ts
  • mcpjam-inspector/client/src/components/evals/helpers.ts
  • mcpjam-inspector/server/routes/v1/environments.ts
  • docs/reference/openapi.json

Comment thread mcpjam-inspector/server/routes/v1/__tests__/hosts.test.ts
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9fbe241e-dedd-4956-bac6-c4e65153bdd6)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcpjam-inspector/client/src/hooks/__tests__/use-run-cost-estimate.test.tsx`:
- Around line 493-501: Extend the “sends environment identities” test coverage
around SuiteProbe and queryCalls to verify that undefined and empty
environmentIds omit the field from query arguments, and add a rerender case
confirming that changing the environment list triggers a new query with updated
identities. Preserve the existing non-empty array assertion.

In `@mcpjam-inspector/server/services/evals-runner.ts`:
- Line 2922: Add regression tests covering local success, local failure, and
hosted success execution paths, including validation of null or empty model
values where applicable. Assert in each path that the effective model identifier
is passed to both finalizeEvalIteration and persistEvalTraceFanout, covering the
modelId assignments near the referenced execution flows.
- Line 3721: Update the hosted finalization flow around modelId so it persists
the resolved modelId rather than test.model. Pass modelId through the hosted
finalization calls on both success and failure paths, preserving the canonical
hosted model identifier for each iteration.

In `@mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts`:
- Around line 114-126: Update safeLaunchReason to reject all four line
terminators—LF, CR, U+2028, and U+2029—while preserving its existing length,
empty-value, and markup checks. Add or confirm tests covering validation
failures and these line-terminator edge cases.
- Around line 182-203: Update launchFailureDetails to reject array-valued
source.details by requiring it to be a non-null object and not
Array.isArray(details) before spreading it into the returned metadata. Add or
confirm a regression test covering a malformed body with details as an array and
verify WebRouteError.details is not populated from that array.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 37068703-9b2e-4a1d-9c4e-f0fdf93fc782

📥 Commits

Reviewing files that changed from the base of the PR and between 9bf0ee8 and edd936a.

📒 Files selected for processing (15)
  • cli/src/commands/environments.ts
  • mcpjam-inspector/client/src/components/evals/__tests__/helpers-explore-sort.test.ts
  • mcpjam-inspector/client/src/components/evals/helpers.ts
  • mcpjam-inspector/client/src/components/evals/run-cost-estimate-hint.tsx
  • mcpjam-inspector/client/src/components/evals/suite-header.tsx
  • mcpjam-inspector/client/src/hooks/__tests__/use-run-cost-estimate.test.tsx
  • mcpjam-inspector/client/src/hooks/use-run-cost-estimate.ts
  • mcpjam-inspector/server/routes/shared/__tests__/evals.test.ts
  • mcpjam-inspector/server/routes/shared/evals.ts
  • mcpjam-inspector/server/services/evals-runner.ts
  • mcpjam-inspector/server/services/evals/finalize-iteration.ts
  • mcpjam-inspector/server/services/evals/recorder.ts
  • mcpjam-inspector/server/services/sessionSimulation/__tests__/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
  • mcpjam-inspector/shared/model-provider.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • mcpjam-inspector/shared/model-provider.ts
  • mcpjam-inspector/server/services/sessionSimulation/tests/launch-journey-run.test.ts
  • cli/src/commands/environments.ts
  • mcpjam-inspector/server/routes/shared/evals.ts
  • mcpjam-inspector/client/src/components/evals/helpers.ts

evaluation,
usage: usageFinal,
messages: acc.conversationMessages,
modelId: test.model,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add persistence tests for all execution paths.

Add regression tests for local success, local failure, and hosted success. Assert that the effective model identifier reaches finalizeEvalIteration and persistEvalTraceFanout.

As per coding guidelines, “All changes should include tests, covering happy paths, validation errors, error handling, and edge cases such as null and empty values.”

Also applies to: 3090-3090, 3721-3721

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcpjam-inspector/server/services/evals-runner.ts` at line 2922, Add
regression tests covering local success, local failure, and hosted success
execution paths, including validation of null or empty model values where
applicable. Assert in each path that the effective model identifier is passed to
both finalizeEvalIteration and persistEvalTraceFanout, covering the modelId
assignments near the referenced execution flows.

Source: Coding guidelines

Comment thread mcpjam-inspector/server/services/evals-runner.ts Outdated
Comment thread mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 24 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="mcpjam-inspector/server/services/evals/finalize-iteration.ts">

<violation number="1" location="mcpjam-inspector/server/services/evals/finalize-iteration.ts:117">
P2: modelId is forwarded to persistEvalTraceFanout but not to the W1 `updateTestIteration` fallback. When the fanout fails before any turn lands (`useW1Fallback`), the whole transcript is written through `updateTestIteration`, which creates a fresh chatSessions row — so the model attribution is dropped there and falls back to `eval/unknown` (see `args.modelId ?? "eval/unknown"` in persist-eval-trace.ts). This is the same bug class the code documents and fixes for `systemPrompt` in the W1 block ("Mirrors appendEvalTurnTrace.systemPrompt"). Forward modelId there too so the W1 path persists the intended model.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

toolsCalled: evaluation.toolsCalled,
usage,
messages,
...(modelId ? { modelId } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: modelId is forwarded to persistEvalTraceFanout but not to the W1 updateTestIteration fallback. When the fanout fails before any turn lands (useW1Fallback), the whole transcript is written through updateTestIteration, which creates a fresh chatSessions row — so the model attribution is dropped there and falls back to eval/unknown (see args.modelId ?? "eval/unknown" in persist-eval-trace.ts). This is the same bug class the code documents and fixes for systemPrompt in the W1 block ("Mirrors appendEvalTurnTrace.systemPrompt"). Forward modelId there too so the W1 path persists the intended model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mcpjam-inspector/server/services/evals/finalize-iteration.ts, line 117:

<comment>modelId is forwarded to persistEvalTraceFanout but not to the W1 `updateTestIteration` fallback. When the fanout fails before any turn lands (`useW1Fallback`), the whole transcript is written through `updateTestIteration`, which creates a fresh chatSessions row — so the model attribution is dropped there and falls back to `eval/unknown` (see `args.modelId ?? "eval/unknown"` in persist-eval-trace.ts). This is the same bug class the code documents and fixes for `systemPrompt` in the W1 block ("Mirrors appendEvalTurnTrace.systemPrompt"). Forward modelId there too so the W1 path persists the intended model.</comment>

<file context>
@@ -111,6 +114,7 @@ export function buildIterationFinishParams(args: {
     toolsCalled: evaluation.toolsCalled,
     usage,
     messages,
+    ...(modelId ? { modelId } : {}),
     ...(systemPrompt ? { systemPrompt } : {}),
     ...(spans?.length ? { spans } : {}),
</file context>

Comment thread mcpjam-inspector/server/routes/v1/hosts.ts Outdated
Comment thread mcpjam-inspector/client/src/components/evals/helpers.ts Outdated
Comment thread mcpjam-inspector/server/routes/v1/evals.ts Outdated
Comment thread mcpjam-inspector/server/services/evals-runner.ts Outdated
Comment thread mcpjam-inspector/server/routes/v1/__tests__/hosts.test.ts Outdated
Snapshots: the runner-parity goldens still expected the pre-fix
`eval/unknown`. Local iterations now record the bare id they called;
hosted iterations record the canonical id they billed; a model-free
case still records nothing (and falls back to `eval/unknown`), because
it never ran a model.

- evals-runner: the hosted finalization persists the RESOLVED `modelId`
  — what `executeTestCase` canonicalized and what the `/stream` call
  actually billed — instead of the authored `test.model`. The local
  finalization gates `modelId` on `caseNeedsModel`, matching the `model`
  field beside it: a model-free case arrives with a display-only
  `pinned-only` sentinel that must not be attributed as a real model.
- launch-journey-run: drop the unused `safeLaunchReason`, which
  duplicated `showableReason`'s policy without a caller. The surviving
  helper rejects every line terminator (`\r`, U+2028, U+2029), not just
  `\n` — `trim` strips those at the ends but not the middle, so a body
  carrying one renders as several lines in a toast. `launchFailureDetails`
  drops an array-shaped `details` rather than spreading it into index
  keys.
- hosts: apply the model trim on BOTH create branches. The normalization
  belongs to the write boundary, not to one of the two ways of reaching
  it — a template carrying a padded id persisted one nothing recognizes.
- v1/evals: trim the id that gets STORED, not just the copy used to
  derive the provider. A padded id previously resolved to the right
  provider and then matched nothing downstream.
- evals helpers: key every iteration when detecting mixed models, not
  only the snapshot-bearing ones. A case mixing snapshot rows with
  pre-snapshot rows is still a mix; keying only snapshots stamped the row
  with one model while the counters folded in another.
- tests: assert the create succeeded before reading the mutation args,
  cover the template trim, the mixed/agreeing pre-snapshot rows, the new
  line-terminator and array-details rejections, and empty/undefined/
  changing `environmentIds` on the suite estimate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_119a3e43-e6c0-4765-a74d-dca77cc2fc33)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcpjam-inspector/server/routes/v1/evals.ts`:
- Around line 1342-1349: Reject whitespace-only model IDs before persistence by
validating the trimmed value in toPersistedModelEntry, while preserving valid
trimmed model/provider behavior and ensuring explicit providers cannot bypass
the rejection. Apply the validation to case and generated-case inputs, and add
regression tests covering whitespace-only IDs with and without an explicit
provider, alongside existing happy-path validation coverage.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c199939-c2fa-4c5f-97ea-1cd2a3248458

📥 Commits

Reviewing files that changed from the base of the PR and between edd936a and 50ba100.

⛔ Files ignored due to path filters (1)
  • mcpjam-inspector/server/services/evals/__tests__/__snapshots__/runner-parity.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (9)
  • mcpjam-inspector/client/src/components/evals/__tests__/helpers-explore-sort.test.ts
  • mcpjam-inspector/client/src/components/evals/helpers.ts
  • mcpjam-inspector/client/src/hooks/__tests__/use-run-cost-estimate.test.tsx
  • mcpjam-inspector/server/routes/v1/__tests__/hosts.test.ts
  • mcpjam-inspector/server/routes/v1/evals.ts
  • mcpjam-inspector/server/routes/v1/hosts.ts
  • mcpjam-inspector/server/services/evals-runner.ts
  • mcpjam-inspector/server/services/sessionSimulation/__tests__/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • mcpjam-inspector/server/routes/v1/tests/hosts.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/tests/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
  • mcpjam-inspector/server/routes/v1/hosts.ts
  • mcpjam-inspector/client/src/components/evals/helpers.ts
  • mcpjam-inspector/server/services/evals-runner.ts

Comment thread mcpjam-inspector/server/routes/v1/evals.ts
`defaultCaseModels` tested `modelId.length > 0` and then trimmed, so a
whitespace-only suite config id passed the guard. It could not actually
reach the return — `providerForModelId` rejects blanks too, so the
provider lookup fails first and this falls through to "no default" — but
that left the guard here correct only by way of another function's
behaviour. Test the value actually returned instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread mcpjam-inspector/server/routes/v1/evals.ts
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_baaf717d-402d-4375-a70f-99a56ab22844)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcpjam-inspector/server/routes/v1/__tests__/eval-edit.test.ts`:
- Around line 976-1000: Extend the parameter table in the whitespace-only model
test to include literal empty-string and null model IDs, covering both relevant
provider configurations as appropriate. Preserve the existing POST request and
assertions requiring a 400 response and no testSuites:createTestCase mutation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9926cb33-b42a-4f8f-b9e2-a17680555778

📥 Commits

Reviewing files that changed from the base of the PR and between 50ba100 and 8dfbd48.

📒 Files selected for processing (4)
  • mcpjam-inspector/server/routes/v1/__tests__/eval-edit.test.ts
  • mcpjam-inspector/server/routes/v1/evals.ts
  • mcpjam-inspector/server/services/sessionSimulation/__tests__/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • mcpjam-inspector/server/services/sessionSimulation/tests/launch-journey-run.test.ts
  • mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts
  • mcpjam-inspector/server/routes/v1/evals.ts

Comment thread mcpjam-inspector/server/routes/v1/__tests__/eval-edit.test.ts Outdated
… branch

`classifyModelIdProvider` ends in a catch-all, so `providerForModelId`
could only fail to name a provider for a BLANK id — and blank is now
rejected up front as a 400. That left two "cannot derive a provider"
throws unreachable, reading as live guards a maintainer would preserve.

Fold the blank rejection into the resolver and give it a total signature
(`string`, not `string | undefined`). `deriveProvider` collapses to the
explicit-or-derived choice it always was, the create-tests mapper loses
its second `if (!provider)`, and the suite-default call site loses an
undefined case it could no longer produce.

Also widen the blank-id rejection table to literal-empty and null ids.
Those never reach the route helper — `z.string().min(1)` rejects them at
the schema — but pinning them keeps the endpoint's contract one statement
("no usable id is a 400") rather than a fact about which of two layers
catches each shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8fcc0e02-5084-4c3f-9fb4-7cf2abb4cbf9)

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f2eeaa52-2f42-41ee-88d6-a61af3b472d6)

Copy link
Copy Markdown
Contributor Author

upsert-preview failed on 75acd98transient infrastructure, not the diff. Needs a re-run by someone with the permission; I don't have it.

The job died in the very first install step, before touching any of this branch's code:

npm install -g @railway/cli@4.57.1 workos@0.17.1
npm error command sh -c node ./npm-install/postinstall.js
npm error https://github.com/railwayapp/cli/releases/download/v4.57.1/railway-v4.57.1-x86_64-unknown-linux-gnu.tar.gz
npm error FetchError: ... failed, reason: socket hang up
npm error   code: 'ECONNRESET',

That's the Railway CLI's postinstall fetching its own release tarball from GitHub and having the connection reset. PREVIEW_URL is empty and HEALTH_OUTCOME: skipped downstream simply because the deploy never got to run. The same job succeeded on f222a3f about 45 minutes earlier with no relevant change in between, and @railway/cli is pinned, so this is a network blip rather than a version problem.

Everything else on 75acd98 is green: Inspector Tests 1–4, Run Tests, Build and Test, E2E Smoke (Playwright), cubic, Snyk. 75acd98 is the merge of origin/main into the branch — zero conflicts, and I verified the merged tree locally before pushing (typecheck clean, server project 5288 passed, swarms client tests 292 passed).

Bugbot's neutral on recent commits is a Cursor usage limit on the account, also unrelated to the code.


Generated by Claude Code

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_79187256-50ff-475d-8441-6c162b576da3)

…anchable model refusal

Review follow-ups on this branch.

**Nine hosted vendors were being attributed to Ollama.** `providerForModelId`
consulted the catalog only for BARE ids, so a qualified id whose vendor is in
the catalog but not in the classifier's 13-entry prefix map — `cohere/`,
`nvidia/`, `amazon/`, `bytedance/`, `stepfun/` and the rest — fell through to
the classifier's `ollama` catch-all. Storing `ollama` then SHORT-CIRCUITS
`assertInlineTestModelsValid` (an open namespace validates nothing), so a
typo'd hosted id stopped being rejected at create time and was dispatched at
a local Ollama instead. The catalog is now consulted for every id, and a
qualified id nothing recognizes keeps its own vendor prefix rather than a
guess.

Split out of that: `attributedProvider` returns undefined where `ollama`
would be a guess rather than knowledge, so `defaultCaseModels` goes back to
"no default — inherit the suite model at run" for a suite id no catalog
knows, instead of durably pinning a generated case to Ollama.

**`PATCH /hosts/:hostId` and `/duplicate` bypassed the model invariant.** A
config PATCH replaces the config, so one call could mint exactly the modelless
host `create` now refuses — and persist a padded id, which `create` trims at
what its own comment calls the write boundary. PATCH now applies the Behavior
tab's rule (clearing a PINNED model is refused; a legacy modelless host stays
editable) and the same trim; duplicate is held to the invariant outright,
since copying a legacy row is otherwise a supported way to keep producing the
state create refuses.

**`ENV_MODEL_REQUIRED` reached eval callers as a bare Convex rejection.** It
is now translated at the recorder — the chokepoint every eval surface shares,
interactive, scheduled and v1 alike — into the same 409 the v1 environments
resolve route emits, `details.reason` included. The swarm launch path now
forwards the backend `code` alongside its details, because the message is
bounded and prose-shaped by design and is the wrong thing to branch on.

Also: a model-free case that THROWS mid-iteration no longer records the
display-only sentinel as its model (the success path 170 lines up already
gated this); a 402 no longer overwrites an earlier, more actionable target
failure; `aggregateSuite` reads its title and its model from the SAME
iteration, so a renamed-then-rerun case cannot show an old title beside a new
model; the raw NUL byte in the classifier test is escaped, so git stops
treating the file as binary; the hosts model requirement is documented in
`openapi.json` and enforced in the SDK's `create_host` schema; and three
doc comments that had detached from what they describe are reattached.

Tests: +7 hosts (PATCH clear/legacy/trim, duplicate both ways), +4 eval-edit
(cohere, nvidia, unknown-vendor prefix, unattributable default), +1 recorder,
+1 launch. Server 5300, shared 581, SDK 4254, CLI 564, mcp 47, client evals
/swarms/hosts 1763. Typecheck clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaGZq4mTuUAGnXDRXoPpXs
@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b40fb31e-7de0-4b2a-8485-b1eb59bb00c8)

Copy link
Copy Markdown
Contributor Author

Pushed 8b8cb5b — review follow-ups on this branch. Backend counterparts are in MCPJam/mcpjam-backend#953 (independent; no wire contract changes).

Two that block merge

providerForModelId attributed nine hosted vendors to Ollama. The catalog lookup was gated behind !id.includes("/"), so a qualified id whose vendor is in the hosted catalog but not in the classifier's 13-entry prefix map — cohere/, nvidia/, amazon/, bytedance/, arcee-ai/, inception/, kwaipilot/, sakana/, stepfun/, xiaomi/ — fell to the ollama catch-all, with MODEL_LOOKUP sitting right there knowing the answer.

The consequence is worse than a mislabel: assertInlineTestModelsValid short-circuits on the open namespaces, so provider: "ollama" skips the hosted-catalog check entirely. A typo'd or unknown hosted id stopped being rejected at create time and got dispatched at a local Ollama. The catalog is now consulted for every id, and a qualified id nothing recognizes keeps its own vendor prefix rather than a guess.

Split out of that fix: attributedProvider returns undefined where ollama would be a guess rather than knowledge. defaultCaseModels goes back to "no default — inherit the suite model at run" for a bare suite id no catalog knows, instead of durably pinning generated cases to Ollama. That restores the pre-PR behavior on that path.

PATCH /hosts/:hostId bypassed the model invariant entirely. updateHostSchema has no model refinement and the route applied neither hostConfigPinsAModel nor withTrimmedModelId, so one PATCH could mint exactly the modelless host create now 400s on — and persist a padded id, at a boundary the create path's own comment calls the write boundary. Confirmed end to end: hosts:updateHost passes input straight to ensureHostConfigV2, whose validator accepts "".

PATCH now applies the Behavior tab's rule — clearing a pinned model is refused, a legacy modelless host stays editable — plus the trim. POST /duplicate is a fourth write boundary and is held to the invariant outright: it mints a host, and copying a legacy row is otherwise a supported way to keep producing the state create refuses.

Also fixed

  • ENV_MODEL_REQUIRED reached eval callers as a bare Convex rejection — only the v1 environments resolve route translated it. Now translated at the recorder, the chokepoint every eval surface shares (interactive, scheduled, v1), into the same 409 with details.reason = "environment_model_required". The swarm launch path forwards the backend code alongside its details: the message is bounded and prose-shaped by design, so it is the wrong thing to branch on.
  • A model-free case that THROWS mid-iteration recorded the sentinel as its model. The success path gates on caseNeedsModel 170 lines up; the failure path did not. The parity suite missed it because its model-free scenario is a setup failure, which finalizes through the gated path.
  • A 402 overwrote an earlier, more actionable target failure (= where every other arm uses ??=). billingBlocked already routes the toast, so nothing needed the clobber.
  • aggregateSuite read its title from the first iteration and its model from the last snapshot-bearing one — a renamed-then-rerun case showed an old title beside a new model, a pairing that never existed. Both now come from one representative iteration.
  • The raw NUL byte in model-provider.test.ts is escaped, so git stops classifying the file as binary and it is diffable and greppable again.
  • The hosts model requirement is now in openapi.json and the SDK's create_host schema — an SDK/agent caller was getting a 400 the published contract never predicted.
  • Three doc comments that had detached from what they describe are reattached (the list-route block above the capabilities route; the launchFailureMessage block above a constant; and the hasOwnProperty rationale, which described an ordinary object where the map has a null prototype — the guard is right, the reason given for it was not).

Verification

Server 5300 · shared 581 · SDK 4254 · CLI 564 · @mcpjam/mcp 47 · client evals+swarms+hosts 1763 · workspace typecheck clean.

New tests: +7 hosts (PATCH clear/legacy/trim, duplicate both ways), +4 eval-edit (cohere, nvidia, unknown-vendor prefix, unattributable suite default), +1 recorder, +1 launch.

One existing assertion changed deliberately: the array-shaped-details test asserted details === undefined, which conflated "the array was dropped" with "there are no details at all". It now pins the actual invariant — no index keys survive — while the code rides alongside.

Not addressed

The environmentIds argument in use-run-cost-estimate still goes to Convex with no capability guard. Against a backend older than #948 the estimate degrades to "unavailable" rather than misreporting, so it fails soft, and a probe on that path costs a round-trip per estimate. Flagging rather than fixing — happy to guard it if you'd like.

The ~1/3 of this branch's diff that is Prettier config drift (trailingComma: "es5" vs main) is also untouched: reformatting it back is churn-for-churn and would conflict with any concurrent edit.


Generated by Claude Code

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 18 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="mcpjam-inspector/server/routes/v1/hosts.ts">

<violation number="1" location="mcpjam-inspector/server/routes/v1/hosts.ts:373">
P2: When a legacy host is pinned by another write after this preflight read, this PATCH can still replace the config without a model and undo that pin. Enforce the legacy-versus-pinned check atomically in `hosts:updateHost` or add a revision/conditional write to this route.</violation>

<violation number="2" location="mcpjam-inspector/server/routes/v1/hosts.ts:445">
P2: When a legacy host contains a padded model ID, this guard treats it as pinned, but `hosts:duplicateHost` copies the config without the new trimming boundary. The duplicate can therefore retain an unrecognized model ID and fail downstream model resolution; normalize the config in the duplicate write path or reject/repair padded source IDs before duplication.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread mcpjam-inspector/server/services/sessionSimulation/launch-journey-run.ts Outdated
// Same rule as the Behavior tab: CLEARING a pinned model is refused, while
// a legacy modelless host stays editable for everything else. That needs
// the pre-edit model, so the read happens only on the branch that needs it.
const current = await readHostDetail(token, projectId, hostId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a legacy host is pinned by another write after this preflight read, this PATCH can still replace the config without a model and undo that pin. Enforce the legacy-versus-pinned check atomically in hosts:updateHost or add a revision/conditional write to this route.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mcpjam-inspector/server/routes/v1/hosts.ts, line 373:

<comment>When a legacy host is pinned by another write after this preflight read, this PATCH can still replace the config without a model and undo that pin. Enforce the legacy-versus-pinned check atomically in `hosts:updateHost` or add a revision/conditional write to this route.</comment>

<file context>
@@ -358,10 +358,32 @@ hosts.patch("/projects/:projectId/hosts/:hostId", async (c) => {
+    // Same rule as the Behavior tab: CLEARING a pinned model is refused, while
+    // a legacy modelless host stays editable for everything else. That needs
+    // the pre-edit model, so the read happens only on the branch that needs it.
+    const current = await readHostDetail(token, projectId, hostId);
+    if (
+      hostConfigPinsAModel(current.config) &&
</file context>

// edit to a legacy row, nothing is stranded by refusing: the source still
// exists, and pinning its model makes the copy legal.
const source = await readHostDetail(token, projectId, hostId);
if (!hostConfigPinsAModel(source.config)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a legacy host contains a padded model ID, this guard treats it as pinned, but hosts:duplicateHost copies the config without the new trimming boundary. The duplicate can therefore retain an unrecognized model ID and fail downstream model resolution; normalize the config in the duplicate write path or reject/repair padded source IDs before duplication.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mcpjam-inspector/server/routes/v1/hosts.ts, line 445:

<comment>When a legacy host contains a padded model ID, this guard treats it as pinned, but `hosts:duplicateHost` copies the config without the new trimming boundary. The duplicate can therefore retain an unrecognized model ID and fail downstream model resolution; normalize the config in the duplicate write path or reject/repair padded source IDs before duplication.</comment>

<file context>
@@ -413,6 +435,21 @@ hosts.post("/projects/:projectId/hosts/:hostId/duplicate", async (c) => {
+  // edit to a legacy row, nothing is stranded by refusing: the source still
+  // exists, and pinning its model makes the copy legal.
+  const source = await readHostDetail(token, projectId, hostId);
+  if (!hostConfigPinsAModel(source.config)) {
+    throw new WebRouteError(
+      400,
</file context>

Comment thread mcpjam-inspector/server/routes/v1/__tests__/eval-edit.test.ts Outdated
Comment thread sdk/src/platform/operations.ts Outdated
Review follow-ups on the launch and host-invariant paths.

- The swarm wave's billing summary rendered `firstError`, so an unrelated
  failure that settled before the 402 replaced the credit-limit copy with a
  transient reason — the "retry the rest" reading the billing branch exists to
  avoid. The 402's message now has its own slot.
- `launchFailureDetails` merged the envelope `code` over the details bag's own,
  replacing an actionable domain code with a transport one. Details wins; the
  envelope only fills a gap.
- The SDK's create-host schema reported a degenerate `config: {}` as a nested
  non-empty-config error while the route reports the XOR. One `superRefine`,
  shaped like the route's, so the schema predicts the 400 the caller receives.
- The vendor-prefix fallback test passed an explicit `provider`, which returns
  verbatim, so the fallback under test never ran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5kwSKq4J7Fqi9JBhCZ1Wy
@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_d079af95-2840-4eed-be72-44992d164f70)

Copy link
Copy Markdown
Contributor Author

Worked the cubic round on 8b8cb5b. Five of the six were real; one isn't. Pushed as 6791378 here and mcpjam-backend@7485cd2.

Fixed

  • Billing summary rendered the wrong reason (new-swarm-create-flow.tsx) — correct, and the inline comment claiming otherwise was wrong: the toast interpolates firstError, so an unrelated failure settling before the 402 replaced the credit-limit copy with a transient one — exactly the "retry the rest" reading the branch exists to prevent. The 402's message now has its own slot (billingError), firstError still gets it as the zero-launch fallback. New test fails without the fix (verified by reverting the one line).
  • Envelope code clobbering a domain details.code (launch-journey-run.ts) — details wins; the envelope code only fills a gap. Test added with both codes present.
  • PATCH TOCTOU on the host model (hosts.ts:373) — the route's read genuinely can't close this, so the check now also lives in hosts:updateHost, where the read and the write share one Convex transaction. The route preflight stays for the better message; the mutation is the backstop. Two backend tests: refuses the clear, leaves a legacy modelless row editable.
  • SDK create-host XOR (operations.ts) — one superRefine shaped like the route's, so config: {} reports the XOR rather than a nested non-empty-config error. Test asserts the exact message the route returns.
  • Vendor-prefix test not exercising the fallback (eval-edit.test.ts) — explicit provider dropped, so it goes through attributedProvider.

Not changing: duplicate + padded model id (hosts.ts:445)

The premise doesn't hold — hosts:duplicateHost doesn't copy the config. It inserts a host pointing at the same hostConfigId (convex/hosts.ts: hostConfigId: host.hostConfigId, plus a hostConfigVersions row for the same id). There's no config write in that path to normalize, so a padded id isn't retained by a copy — the copy is the source's config document, and trimming it there would rewrite the source host's identity too. Padded ids in existing rows predate the trimming boundary; that boundary belongs on the write paths (create / PATCH), which is where this PR puts it.

Verification: workspace typecheck ✅ · SDK 4257 passed ✅ · Inspector --project server + --project shared 5941 passed ✅ · SwarmsTab.createFlow 56 passed ✅ · backend typecheck + check:mirrors ✅ · full backend suite 6378 passed ✅. Also merged the latest main (8 commits) into the branch.


Generated by Claude Code

@chelojimenez
chelojimenez merged commit e7c8d62 into main Aug 13, 2026
21 checks passed
@chelojimenez
chelojimenez deleted the claude/environment-model-matrix-b4gyef branch August 13, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants