Skip to content

fix: gate ?q= auto-send on the provisioned sessionId (chat#1847)#1848

Merged
sweetmantech merged 1 commit into
mainfrom
fix/q-autosend-session-race
Jul 4, 2026
Merged

fix: gate ?q= auto-send on the provisioned sessionId (chat#1847)#1848
sweetmantech merged 1 commit into
mainfrom
fix/q-autosend-session-race

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1847 — customer-reported (2026-07-04): clicking any AGENTS card as a logged-in user showed the prefilled message, then "An error occurred, please try again!".

Root cause

The ?q= deep-link auto-send effect in useVercelChat.ts gated on authenticated + useChat ready but not on the new-chat bootstrap — it fired while sessionId was still undefined, so the transport POSTed /api/chat with no sessionId and the api validator 400'd (missing_fields: ["sessionId"]) → generic error toast. The manual Send button was already correctly gated on provisioning (#1747/#1767); the auto-send path skipped that gate and lost the race every time for logged-in users. Captured live in the issue: the failing POST /api/chat lands before POST /api/sessions returns.

What

  • lib/chat/shouldAutoSendInitialMessage.ts — the gate extracted as a pure function (SRP), mirroring the manual-send conditions plus sessionId.
  • hooks/useVercelChat.ts — the auto-send effect uses it, with sessionId added to the dep array: when the bootstrap lands, the effect re-runs and the queued ?q= message sends. No UX change beyond "it works" — the message simply waits for the workspace dot to go ready, same as manual send.

Tests (TDD, RED→GREEN)

6-case unit suite for the gate: happy path, provisioning race (the #1847 case), no initial message, busy transport (double-send guard), already-has-messages, pre-auth. Full repo suite 80 passed; tsc --noEmit no new errors vs main (22 pre-existing); prettier clean.

Verification

Preview verification (real login → /chat?q=… → assistant responds, POST /api/chat only after POST /api/sessions) to follow as a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Fixes #1847: the /chat?q= auto-send now waits for a provisioned sessionId, preventing 400s and the generic error toast for logged-in users. The initial message queues and sends once the new chat session is ready.

  • Bug Fixes

    • Gate deep-link auto-send using a new shouldAutoSendInitialMessage check that mirrors manual-send and requires a valid sessionId.
    • Add sessionId to the effect dependencies so it re-runs after bootstrap; send occurs only when authenticated, status is ready, and no prior messages exist.
  • Refactors

    • Extracted the gate into lib/chat/shouldAutoSendInitialMessage (pure function).
    • Added a 6-case unit test suite covering the provisioning race and related states.

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

Review in cubic

Summary by CodeRabbit

  • New Features

    • Improved auto-sending of deep-linked initial chat messages so they only send when the chat is ready and the session is available.
    • Added stricter checks to avoid sending messages in incomplete or unauthenticated chat states.
  • Bug Fixes

    • Reduced cases where an initial message could send too early or when chat history already contains content.

The deep-link initial message (AGENTS cards route to /chat?q=...) fired
as soon as the user was authenticated and useChat was idle - before the
new-chat bootstrap minted a sessionId - so the transport POSTed
/api/chat without a sessionId, the api validator 400'd
(missing_fields: [sessionId]), and the user saw the generic error toast.
The manual Send button was already gated on provisioning; the auto-send
path skipped that gate and lost the race every time for logged-in users.

Extract the gate into lib/chat/shouldAutoSendInitialMessage (SRP,
unit-tested) mirroring the manual-send conditions plus sessionId. The
effect re-runs when the bootstrap lands, so the ?q= message queues and
sends instead of failing.

TDD: 6-case suite RED->GREEN (incl. the provisioning race and the
busy-transport double-send guard). Full suite 80 passed; tsc no new
errors vs main (22 pre-existing); prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

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.

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Jul 4, 2026 4:27pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds shouldAutoSendInitialMessage in lib/chat/shouldAutoSendInitialMessage.ts, encapsulating gating logic for auto-sending a deep-link initial message based on status, message count, authentication, and session provisioning. useVercelChat.ts now uses this helper and includes sessionId in its effect dependencies; unrelated formatting changes touch the mention-files effect.

Changes

Auto-send gating fix

Layer / File(s) Summary
Gating contract and logic
lib/chat/shouldAutoSendInitialMessage.ts
Adds ShouldAutoSendInitialMessageParams and shouldAutoSendInitialMessage, returning true only when there are no initial messages, status is "ready", message count is ≤1, user is authenticated, and sessionId is present.
Wire gating into auto-send effect
hooks/useVercelChat.ts
Imports and calls shouldAutoSendInitialMessage with status, messages.length, userId, authenticated, and sessionId, replacing manual gating; adds sessionId to the effect's dependency array.
Mention-files effect formatting
hooks/useVercelChat.ts
Reformats cancelled guards and multiline structure in the mention-files resolution effect without changing behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • recoupable/chat#1774: Also modifies hooks/useVercelChat.ts around provisioned session/chat id lifecycle that the new auto-send gating depends on.

Poem

A rabbit waits for sessionId to land,
before it sends the message in your hand.
No more races, no more toast,
the deep-link ping arrives at the right coast. 🐇✅

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The auto-send gate now waits for sessionId, but the required regression test cannot be verified because the test file was excluded by !**/.test.. Review lib/chat/tests/shouldAutoSendInitialMessage.test.ts, excluded by !**/.test., to confirm the sessionId undefined→defined regression coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes stay focused on the auto-send race fix and related refactoring, with no clear unrelated behavior added.
Solid & Clean Code ✅ Passed PASS: The refactor isolates the auto-send gate into a small pure function, keeps the hook change focused, and adds clear tests without extra complexity.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/q-autosend-session-race

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.

🧹 Nitpick comments (2)
lib/chat/shouldAutoSendInitialMessage.ts (2)

1-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten status to the actual union type instead of string.

status: string accepts any value, so a typo like "redy" would silently fail the === "ready" check with no compile-time warning. The useChat hook exposes status as a literal union ("submitted" | "streaming" | "ready" | "error"); importing/reusing that type here would give compile-time safety for this critical gate.

As per path instructions, lib/**/*.ts should "Use TypeScript for type safety."

♻️ Suggested fix
+import type { ChatStatus } from "ai"; // verify exact export name/version
+
 interface ShouldAutoSendInitialMessageParams {
   hasInitialMessages: boolean;
   /** useChat transport status — only "ready" may send. */
-  status: string;
+  status: ChatStatus;
🤖 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 `@lib/chat/shouldAutoSendInitialMessage.ts` around lines 1 - 16, The
ShouldAutoSendInitialMessageParams status field is too broad and should match
the actual useChat status union instead of string. Update the type used by
shouldAutoSendInitialMessage to reuse the useChat status literal union
("submitted" | "streaming" | "ready" | "error"), so the ready check is
compile-time safe and typos are rejected. Keep the change focused on the
ShouldAutoSendInitialMessageParams interface and any related type import or
alias needed in shouldAutoSendInitialMessage.

Source: Path instructions


32-37: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Gate has no memory of "already attempted" — possible re-fire after a retry truncates messages.

The gate only inspects instantaneous messagesLength/status; it doesn't know whether the initial message was already sent once. In useVercelChat.ts, a failed auto-sent message can be truncated back out of messages via deleteTrailingMessages during a manual retry (see earliestFailedUserMessageId flow, hooks/useVercelChat.ts lines 232-250, 353-358). If messagesLength drops back to 0 while status transiently reads "ready", this gate would return true again and the effect would re-fire initialMessages[0], duplicating the send alongside the user's manual retry.

Official useChat docs state status only becomes "ready" once "the full response has been received and processed," which limits — but doesn't fully rule out — this race depending on exact retry timing. Worth confirming with a regression test around the retry-after-auto-send-failure path, since the PR already adds tests for the provisioning race.

🤖 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 `@lib/chat/shouldAutoSendInitialMessage.ts` around lines 32 - 37, The
shouldAutoSendInitialMessage gate is stateless and can re-fire the initial send
after a retry truncates messages back to empty. Update
shouldAutoSendInitialMessage to also consult a persisted “already
attempted/sent” signal rather than only messagesLength, status, authenticated,
userId, and sessionId, and wire that state through useVercelChat so
deleteTrailingMessages and the earliestFailedUserMessageId retry flow cannot
trigger a duplicate initialMessages[0] send. Add a regression test covering the
auto-send-failure retry path to confirm the initial message is not sent twice.
🤖 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.

Nitpick comments:
In `@lib/chat/shouldAutoSendInitialMessage.ts`:
- Around line 1-16: The ShouldAutoSendInitialMessageParams status field is too
broad and should match the actual useChat status union instead of string. Update
the type used by shouldAutoSendInitialMessage to reuse the useChat status
literal union ("submitted" | "streaming" | "ready" | "error"), so the ready
check is compile-time safe and typos are rejected. Keep the change focused on
the ShouldAutoSendInitialMessageParams interface and any related type import or
alias needed in shouldAutoSendInitialMessage.
- Around line 32-37: The shouldAutoSendInitialMessage gate is stateless and can
re-fire the initial send after a retry truncates messages back to empty. Update
shouldAutoSendInitialMessage to also consult a persisted “already
attempted/sent” signal rather than only messagesLength, status, authenticated,
userId, and sessionId, and wire that state through useVercelChat so
deleteTrailingMessages and the earliestFailedUserMessageId retry flow cannot
trigger a duplicate initialMessages[0] send. Add a regression test covering the
auto-send-failure retry path to confirm the initial message is not sent twice.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8fb20e91-e80c-4d5f-90ae-4f55a22a9e00

📥 Commits

Reviewing files that changed from the base of the PR and between 004b9de and bbef98d.

⛔ Files ignored due to path filters (1)
  • lib/chat/__tests__/shouldAutoSendInitialMessage.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (2)
  • hooks/useVercelChat.ts
  • lib/chat/shouldAutoSendInitialMessage.ts

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview verification — 2026-07-04 ✅

Preview https://chat-i9jdfg112-recoup.vercel.app confirmed built from PR head bbef98dc (deployment 5311546539 queried by sha, success). Tested via Chrome DevTools MCP with a real staff login, using the exact customer repro URL from #1847 (/chat?q=<AGENTS prompt>).

broken (prod, #1847 capture) fixed (this preview)
behavior after login message auto-sent while status still read "Preparing your workspace" (Send button disabled) message queued — nothing sent during provisioning, then fired the moment the workspace went ready
network order POST /api/chat before POST /api/sessions POST /api/sessions (reqid 333) → 200, then POST /api/chat (reqid 351)
POST /api/chat result 400 {"missing_fields":["sessionId"]} 200, response streamed
UX "An error occurred, please try again!" toast, dead chat URL flipped to canonical /sessions/79bd4bf8…/chats/61fda351…, status "Workspace ready.", assistant streamed a full multi-step response (skill loads → bash → follow-up questions). Zero error toasts.

Also exercised the no-double-send guard implicitly: the effect re-ran across several provisioning re-renders and sent exactly once.

Environment note for future preview testing: chat previews point at test-recoup-api.vercel.app and the preview Privy app (cmc52us2g…), so a fresh login on the preview domain is required — sessions from chat.recoupable.dev don't carry over.

Unit suite: 6 cases RED→GREEN incl. the provisioning race; full repo suite 80 passed; tsc no new errors vs main; prettier clean. Ready for review/merge (docs→none needed; client-only fix).

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

Confidence score: 3/5

  • lib/chat/shouldAutoSendInitialMessage.ts can re-trigger the ?q= auto-send when the first attempt leaves only the user message in state, which risks duplicate initial messages and repeated downstream processing in chat sessions; tighten the gate to treat an existing user-only initial message as already handled (or persist an explicit "initial message sent" flag) before merging.
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/chat/shouldAutoSendInitialMessage.ts">

<violation number="1" location="lib/chat/shouldAutoSendInitialMessage.ts:34">
P2: The `?q=` message can be sent again after a first attempt leaves only the user message in state, because this gate only blocks conversations with more than one message. Since `useChat` is not initialized with `initialMessages`, consider treating any existing message as “already sent” (`messagesLength > 0`).</violation>
</file>

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

Re-trigger cubic

}: ShouldAutoSendInitialMessageParams): boolean {
if (!hasInitialMessages) return false;
if (status !== "ready") return false;
if (messagesLength > 1) return false;

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: The ?q= message can be sent again after a first attempt leaves only the user message in state, because this gate only blocks conversations with more than one message. Since useChat is not initialized with initialMessages, consider treating any existing message as “already sent” (messagesLength > 0).

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

<comment>The `?q=` message can be sent again after a first attempt leaves only the user message in state, because this gate only blocks conversations with more than one message. Since `useChat` is not initialized with `initialMessages`, consider treating any existing message as “already sent” (`messagesLength > 0`).</comment>

<file context>
@@ -0,0 +1,38 @@
+}: ShouldAutoSendInitialMessageParams): boolean {
+  if (!hasInitialMessages) return false;
+  if (status !== "ready") return false;
+  if (messagesLength > 1) return false;
+  if (!userId || !authenticated) return false;
+  if (!sessionId) return false;
</file context>
Suggested change
if (messagesLength > 1) return false;
if (messagesLength > 0) return false;

@sweetmantech sweetmantech merged commit aa49f75 into main Jul 4, 2026
4 checks passed
@sweetmantech sweetmantech deleted the fix/q-autosend-session-race branch July 4, 2026 16:38
sweetmantech added a commit that referenced this pull request Jul 4, 2026
…n extension hook (OCP)

Review feedback on #1848/#1849: useVercelChat grew in both PRs, being
modified for every deep-link change instead of extended. Move the whole
?q= behavior (prefill + provisioning-gated auto-fire + one-shot guards)
into hooks/useInitialMessageAutoSend, composed like the existing
extension hooks (useChatTransport, usePersistSelectedModel,
useMessageLoader). useVercelChat now carries one hook call and is
smaller than it was before #1848 (422 lines vs 424); future deep-link
changes touch only the dedicated hook.

No behavior change: the hook is a verbatim move of the two effects over
the tested pure helpers (shouldAutoSendInitialMessage,
getInitialMessageText). Full suite 83 passed; tsc no new errors vs
main; prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sweetmantech added a commit that referenced this pull request Jul 4, 2026
…1848 follow-up) (#1849)

* feat: prefill the input with the ?q= prompt while provisioning (chat#1848 follow-up)

After #1848 the deep-link message correctly waits for the session, but
the wait is dead space: nothing on screen tells the customer their
AGENTS click worked. Prefill the input with the prompt immediately, then
auto-fire when the workspace goes ready — sending whatever is in the
input at that moment, so an edit made while waiting sends the edited
text and a cleared input sends nothing, exactly like pressing Send.

- lib/chat/getInitialMessageText.ts (SRP, unit-tested): first text part
  of the deep-link message, undefined when nothing meaningful.
- useVercelChat: one-shot prefill effect (never clobbers an existing
  conversation or typed text) + auto-fire builds the message from the
  live input; one-shot ref prevents double-sends.

TDD RED->GREEN (3-case extractor suite); full suite 83 passed; tsc no
new errors vs main; prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: extract useInitialMessageAutoSend — deep-link behavior as an extension hook (OCP)

Review feedback on #1848/#1849: useVercelChat grew in both PRs, being
modified for every deep-link change instead of extended. Move the whole
?q= behavior (prefill + provisioning-gated auto-fire + one-shot guards)
into hooks/useInitialMessageAutoSend, composed like the existing
extension hooks (useChatTransport, usePersistSelectedModel,
useMessageLoader). useVercelChat now carries one hook call and is
smaller than it was before #1848 (422 lines vs 424); future deep-link
changes touch only the dedicated hook.

No behavior change: the hook is a verbatim move of the two effects over
the tested pure helpers (shouldAutoSendInitialMessage,
getInitialMessageText). Full suite 83 passed; tsc no new errors vs
main; prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: source auth state inside useInitialMessageAutoSend (KISS)

authenticated/userId come from usePrivy/useUserProvider directly instead
of riding through params — the hook's params now carry only what is
chat-instance-scoped (initialMessages, status, messagesLength,
sessionId, input/setInput, send). Suite 83 passed; tsc clean vs main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

?q= deep-link auto-send races session provisioning → 400 + error toast (AGENTS cards broken for logged-in users)

1 participant