Skip to content

fix(runs): persist the resolved modelId onto the chat row - #804

Open
sweetmantech wants to merge 1 commit into
mainfrom
fix/persist-model-id
Open

fix(runs): persist the resolved modelId onto the chat row#804
sweetmantech wants to merge 1 commit into
mainfrom
fix/persist-model-id

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes the "persist the resolved modelId" row of chat#1918. No docs change — no contract or response shape changes.

The bug

handleStartChatRun.ts L40 resolves modelId and passes it to the workflow at L59, but the provisionRunSession call at L44 omitted it:

const provisioned = await provisionRunSession({
  accountId,
  title: DEFAULT_RUN_SESSION_TITLE,
  artistId,          // <-- no modelId
});

createSessionWithInitialChat then inserted the chat with no model_id. Because the generation path reads the model from the chat row (handleChatWorkflowStream.ts L93: chat.model_id ?? DEFAULT_MODEL_ID), chats.model_id recorded the default for every headless run.

Impact: the column lies

Measured on prod, scheduled-generation rooms over 30 days (test accounts excluded):

Actual model (generation metadata) Runs chats.model_id says
anthropic/claude-sonnet-5 746 anthropic/claude-haiku-4.5
moonshotai/kimi-k3 238 anthropic/claude-haiku-4.5
openai/gpt-5.4-mini 55 anthropic/claude-haiku-4.5
anthropic/claude-haiku-4.5 46 anthropic/claude-haiku-4.5
anthropic/claude-sonnet-4.5 7 anthropic/claude-haiku-4.5
gpt-4o-mini 6 anthropic/claude-haiku-4.5

The runs are correct — the configured model is applied. Only the recorded column is wrong, which silently invalidates any audit or dashboard keyed on chats.model_id. This also corrects the hypothesis originally written on the issue ("the customer's selection is not reaching the run"), which the data disproves.

The fix

Thread modelId through the three layers it was already available in:

  • handleStartChatRun → passes modelId to provisionRunSession
  • provisionRunSession → forwards it to createSessionWithInitialChat
  • createSessionWithInitialChat → sets model_id on the insert, omitted entirely when undefined so existing behavior is unchanged for callers that don't supply one

Tests

RED→GREEN at each layer (4 new tests):

test layer
persists modelId onto the chat row createSessionWithInitialChat
omits model_id when no modelId is supplied createSessionWithInitialChat
forwards modelId to createSessionWithInitialChat provisionRunSession
passes the resolved modelId to provisionRunSession handleStartChatRun (the bug site)

The handleStartChatRun test was verified genuinely RED by removing the modelId argument and confirming an AssertionError (not a setup error), then GREEN on restore.

  • pnpm exec vitest run lib/chat lib/sessions lib/agent136 files, 936 tests — all pass with the fix applied
  • pnpm exec eslint lib/sessions lib/chat/runs → clean
  • pnpm exec tsc --noEmit → no errors in any touched file

Verification note

Done-when in the issue asks that a task set to a non-default model records that model on the chat and in usage_events. This PR fixes the chat row. Whether usage_events.model_id derives from the same source should be confirmed against a preview run before the issue row is closed.

🤖 Generated with Claude Code


Summary by cubic

Persist the resolved modelId to the chat row so headless runs record the correct model. Fixes the bug from chat#1918 where chats.model_id always stored the default.

  • Bug Fixes
    • Thread modelId from handleStartChatRunprovisionRunSessioncreateSessionWithInitialChat.
    • Set model_id on chat insert when provided; omit when undefined.
    • Added tests to verify propagation and accurate recording.
    • No API or response shape changes; confirm usage_events.model_id in preview per chat#1918.

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

Review in cubic

handleStartChatRun resolved modelId and threaded it into the workflow input,
but called provisionRunSession without it, so createSessionWithInitialChat
inserted the chat with no model_id. The generation path reads the model from
the chat row, so chats.model_id recorded the default for every headless run
regardless of what the task configured.

Measured on prod over 30 days: chats.model_id reads anthropic/claude-haiku-4.5
for 100% of scheduled-generation rooms, while the generation metadata shows
746 runs on sonnet-5, 238 on kimi-k3 and 55 on gpt-5.4-mini. The runs were
correct; the column was not — so every audit keyed on it was wrong.

chat#1918
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Jul 31, 2026 12:32am

Request Review

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 46f2a7a9-708d-431e-8e62-ef1d7b400ded

📥 Commits

Reviewing files that changed from the base of the PR and between f38823c and da5a92e.

⛔ Files ignored due to path filters (3)
  • lib/chat/runs/__tests__/handleStartChatRun.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chat/runs/__tests__/provisionRunSession.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/sessions/__tests__/createSessionWithInitialChat.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (3)
  • lib/chat/runs/handleStartChatRun.ts
  • lib/chat/runs/provisionRunSession.ts
  • lib/sessions/createSessionWithInitialChat.ts

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.

@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 6 files

Confidence score: 4/5

  • In lib/sessions/createSessionWithInitialChat.ts, the createSessionWithInitialChat model selection path treats an explicit modelId: "" as missing, which can override caller intent and route sessions to an unintended fallback model; switch the check to undefined (or explicitly reject empty IDs) so provided values are handled consistently.
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="lib/sessions/createSessionWithInitialChat.ts">

<violation number="1" location="lib/sessions/createSessionWithInitialChat.ts:65">
P2: An explicitly supplied `modelId: ""` is treated as absent, despite the stated undefined-only fallback. Check for `undefined` rather than truthiness so supplied values are forwarded consistently (or reject empty IDs at validation).</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as HTTP Client/Caller
    participant Handler as handleStartChatRun
    participant Provisioner as provisionRunSession
    participant Creator as createSessionWithInitialChat
    participant DB as Database
    
    Note over Handler,DB: Headless Run Session Creation Flow
    
    Client->>Handler: POST /chat/run (authenticated)
    Handler->>Handler: Resolve modelId from task configuration
    Handler->>Provisioner: provisionRunSession({accountId, title, artistId, modelId})
    Note over Handler,Provisioner: modelId now forwarded (was omitted)
    
    Provisioner->>Creator: createSessionWithInitialChat({accountId, title, chatTitle, artistId, modelId})
    Note over Provisioner,Creator: modelId passed through
    
    Creator->>DB: Insert session
    DB-->>Creator: Session record
    
    alt modelId provided
        Creator->>DB: insertChat({id, session_id, title, model_id: modelId})
        Note over Creator,DB: Sets chats.model_id to resolved model
    else no modelId
        Creator->>DB: insertChat({id, session_id, title})
        Note over Creator,DB: model_id omitted (default behavior)
    end
    
    DB-->>Creator: Chat record
    alt Chat insert failed
        Creator->>DB: deleteSessionById(session.id)
        DB-->>Creator: Rollback complete
        Creator-->>Provisioner: {ok: false, reason: "insert"}
        Provisioner-->>Handler: Throw error
    else Success
        Creator-->>Provisioner: Session with initial chat
        Provisioner-->>Handler: ProvisionedRunSession
    end
    
    Note over Client,DB: Later: Chat generation reads model
    
    Handler->>Handler: mintEphemeralAccountKey()
    Handler-->>Client: Response with session
    
    Note over Client,DB: Downstream: generation reads chats.model_id
    Note over DB: chats.model_id now accurate (was always default)
Loading

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

Re-trigger cubic

id: generateUUID(),
session_id: session.id,
title: chatTitle,
...(modelId ? { model_id: 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: An explicitly supplied modelId: "" is treated as absent, despite the stated undefined-only fallback. Check for undefined rather than truthiness so supplied values are forwarded consistently (or reject empty IDs at validation).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/sessions/createSessionWithInitialChat.ts, line 65:

<comment>An explicitly supplied `modelId: ""` is treated as absent, despite the stated undefined-only fallback. Check for `undefined` rather than truthiness so supplied values are forwarded consistently (or reject empty IDs at validation).</comment>

<file context>
@@ -50,7 +58,12 @@ export async function createSessionWithInitialChat({
+    id: generateUUID(),
+    session_id: session.id,
+    title: chatTitle,
+    ...(modelId ? { model_id: modelId } : {}),
+  });
   if (!chat) {
</file context>
Suggested change
...(modelId ? { model_id: modelId } : {}),
...(modelId !== undefined ? { model_id: modelId } : {}),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant