Skip to content

Commit 6de3c44

Browse files
committed
fix(deps): revert next to 16.2.12, its 16.3.0 optimizer deletes live code
Next 16.3.0's Turbopack optimizer models a bare `return <asyncCall>()` tail call inside an async function as returning the promise object, then propagates that always-truthy fact through the caller's `await`. Where the result feeds an `if (x)` whose every branch returns, it concludes the branch is always taken and deletes everything after it from the emitted bundle. Two sites shipped to production that way: - `POST /api/credentials` lost its entire create path — the transaction, the org locks, the insert, the audit, the 201. A first-time create fell into the existing-credential branch and threw on `existingCredential.id`, so every new credential 500'd. - `upsertAsyncToolCall` collapsed to `async () => await getAsyncToolCall(id)`. The insert is simply gone; it returns null for every new async copilot tool call. Silent — no error, no failed request. A differential scan of 71,266 source string literals across `.next/server` and `.next/static`, comparing images built from the same commit on 16.2.12 and 16.3.0, found exactly these two and nothing else. That scan cannot see dropped branches with no distinctive string literal, which is why the version goes back rather than the two sites being patched alone. Both are also hardened with `return await`, verified to defeat the miscompile in a minimal reproduction. The TypeScript toolchain cleanup from the original bump (dropping @typescript/native-preview, `useTypeScriptCli`) is kept.
1 parent 40cbee4 commit 6de3c44

8 files changed

Lines changed: 128 additions & 49 deletions

File tree

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"fumadocs-openapi": "10.8.1",
3232
"fumadocs-ui": "16.8.5",
3333
"lucide-react": "^0.511.0",
34-
"next": "16.3.0",
34+
"next": "16.2.12",
3535
"next-themes": "^0.4.6",
3636
"react": "19.2.4",
3737
"react-dom": "19.2.4",

apps/sim/app/api/credentials/route.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,24 @@ async function findExistingCredentialBySourceWith(
133133
return null
134134
}
135135

136+
/**
137+
* `return await` is load-bearing, not redundant. Next 16.3.0's Turbopack
138+
* optimizer models a bare `return <asyncCall>()` tail call as returning the
139+
* promise object, then propagates that always-truthy fact through the caller's
140+
* `await`. It concludes `if (existingCredential)` is always taken and — because
141+
* every branch inside that block returns — deletes the entire create path from
142+
* the emitted bundle, so a first-time create throws on `existingCredential.id`.
143+
* Awaiting here makes the optimizer model the resolved value instead.
144+
*/
136145
async function findExistingCredentialBySource(params: ExistingCredentialSourceParams) {
137-
return findExistingCredentialBySourceWith(db, params)
146+
return await findExistingCredentialBySourceWith(db, params)
138147
}
139148

140149
async function findExistingCredentialBySourceTx(
141150
tx: Parameters<Parameters<typeof db.transaction>[0]>[0],
142151
params: ExistingCredentialSourceParams
143152
) {
144-
return findExistingCredentialBySourceWith(tx, params)
153+
return await findExistingCredentialBySourceWith(tx, params)
145154
}
146155

147156
export const GET = withRouteHandler(async (request: NextRequest) => {

apps/sim/lib/copilot/async-runs/repository.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,18 @@ const WORKFLOW_EXECUTION_CLAIM_PREFIX = 'workflow:'
2929
// can evaluate modules before instrumentation-node.ts finishes).
3030
const getAsyncRunsTracer = () => trace.getTracer('sim-copilot-async-runs', '1.0.0')
3131

32-
// Wrap an async DB op in a client-kind span with canonical `db.*` attrs.
33-
// Cancellation is routed through `markSpanForError` so aborts record the
34-
// exception event but don't paint spans red.
32+
/**
33+
* Wrap an async DB op in a client-kind span with canonical `db.*` attrs.
34+
* Cancellation is routed through `markSpanForError` so aborts record the
35+
* exception event but don't paint spans red.
36+
*
37+
* Every caller writes `return await withDbSpan(...)`. The `await` is
38+
* load-bearing, not redundant: Next 16.3.0's Turbopack optimizer models a bare
39+
* `return <asyncCall>()` tail call as returning the promise object, then
40+
* propagates that always-truthy fact through the caller's `await`. It deleted
41+
* the entire insert path from `upsertAsyncToolCall` in the shipped bundle
42+
* because `if (existing) return existing` looked always-taken.
43+
*/
3544
async function withDbSpan<T>(
3645
name: string,
3746
op: string,
@@ -74,7 +83,7 @@ export interface CreateRunSegmentInput {
7483
}
7584

7685
export async function createRunSegment(input: CreateRunSegmentInput) {
77-
return withDbSpan(
86+
return await withDbSpan(
7887
TraceSpan.CopilotAsyncRunsCreateRunSegment,
7988
'INSERT',
8089
'copilot_runs',
@@ -122,7 +131,7 @@ export async function updateRunStatus(
122131
requestContext?: Record<string, unknown>
123132
} = {}
124133
) {
125-
return withDbSpan(
134+
return await withDbSpan(
126135
TraceSpan.CopilotAsyncRunsUpdateRunStatus,
127136
'UPDATE',
128137
'copilot_runs',
@@ -150,7 +159,7 @@ export async function updateRunStatus(
150159
}
151160

152161
async function getLatestRunForExecution(executionId: string) {
153-
return withDbSpan(
162+
return await withDbSpan(
154163
TraceSpan.CopilotAsyncRunsGetLatestForExecution,
155164
'SELECT',
156165
'copilot_runs',
@@ -183,7 +192,7 @@ export async function getLatestRunForStream(streamId: string, userId?: string) {
183192
}
184193

185194
export async function getRunSegment(runId: string) {
186-
return withDbSpan(
195+
return await withDbSpan(
187196
TraceSpan.CopilotAsyncRunsGetRunSegment,
188197
'SELECT',
189198
'copilot_runs',
@@ -213,7 +222,7 @@ async function createRunCheckpoint(input: {
213222
agentState: Record<string, unknown>
214223
providerRequest: Record<string, unknown>
215224
}) {
216-
return withDbSpan(
225+
return await withDbSpan(
217226
TraceSpan.CopilotAsyncRunsCreateRunCheckpoint,
218227
'INSERT',
219228
'copilot_run_checkpoints',
@@ -247,7 +256,7 @@ export async function upsertAsyncToolCall(input: {
247256
status?: CopilotAsyncToolStatus
248257
sealedContext?: AsyncCompletionData
249258
}) {
250-
return withDbSpan(
259+
return await withDbSpan(
251260
TraceSpan.CopilotAsyncRunsUpsertAsyncToolCall,
252261
'UPSERT',
253262
'copilot_async_tool_calls',
@@ -296,7 +305,7 @@ export async function upsertAsyncToolCall(input: {
296305
}
297306

298307
export async function getAsyncToolCall(toolCallId: string) {
299-
return withDbSpan(
308+
return await withDbSpan(
300309
TraceSpan.CopilotAsyncRunsGetAsyncToolCall,
301310
'SELECT',
302311
'copilot_async_tool_calls',
@@ -324,7 +333,7 @@ async function markAsyncToolStatus(
324333
} = {},
325334
expectedStatuses?: CopilotAsyncToolStatus[]
326335
) {
327-
return withDbSpan(
336+
return await withDbSpan(
328337
TraceSpan.CopilotAsyncRunsMarkAsyncToolStatus,
329338
'UPDATE',
330339
'copilot_async_tool_calls',
@@ -382,7 +391,7 @@ export function getClaimedWorkflowExecutionId(claimedBy: string | null | undefin
382391

383392
export async function claimWorkflowToolExecution(toolCallId: string, executionId: string) {
384393
const claimedBy = `${WORKFLOW_EXECUTION_CLAIM_PREFIX}${executionId}`
385-
return withDbSpan(
394+
return await withDbSpan(
386395
TraceSpan.CopilotAsyncRunsMarkAsyncToolStatus,
387396
'UPDATE',
388397
'copilot_async_tool_calls',
@@ -426,7 +435,7 @@ export async function claimWorkflowToolExecution(toolCallId: string, executionId
426435

427436
export async function releaseWorkflowToolExecutionClaim(toolCallId: string, executionId: string) {
428437
const claimedBy = `${WORKFLOW_EXECUTION_CLAIM_PREFIX}${executionId}`
429-
return withDbSpan(
438+
return await withDbSpan(
430439
TraceSpan.CopilotAsyncRunsReleaseClaim,
431440
'UPDATE',
432441
'copilot_async_tool_calls',
@@ -464,7 +473,7 @@ export async function releaseWorkflowToolExecutionClaim(toolCallId: string, exec
464473
* cannot click, type, submit, or navigate twice.
465474
*/
466475
export async function claimPendingAsyncToolCall(toolCallId: string, claimedBy: string) {
467-
return withDbSpan(
476+
return await withDbSpan(
468477
TraceSpan.CopilotAsyncRunsMarkAsyncToolStatus,
469478
'UPDATE',
470479
'copilot_async_tool_calls',
@@ -545,7 +554,7 @@ export async function replaceTerminalAsyncToolCallResult(input: {
545554
result: AsyncCompletionData | null
546555
error: string | null
547556
}) {
548-
return withDbSpan(
557+
return await withDbSpan(
549558
TraceSpan.CopilotAsyncRunsMarkAsyncToolStatus,
550559
'UPDATE',
551560
'copilot_async_tool_calls',
@@ -588,7 +597,7 @@ export async function recordToolPermissionDecision(
588597
toolCallId: string,
589598
decision: CopilotToolPermissionDecision
590599
) {
591-
return withDbSpan(
600+
return await withDbSpan(
592601
TraceSpan.CopilotAsyncRunsMarkAsyncToolStatus,
593602
'UPDATE',
594603
'copilot_async_tool_calls',
@@ -619,7 +628,7 @@ export async function recordToolPermissionDecision(
619628
}
620629

621630
async function listAsyncToolCallsForRun(runId: string) {
622-
return withDbSpan(
631+
return await withDbSpan(
623632
TraceSpan.CopilotAsyncRunsListForRun,
624633
'SELECT',
625634
'copilot_async_tool_calls',
@@ -635,7 +644,7 @@ async function listAsyncToolCallsForRun(runId: string) {
635644

636645
export async function getAsyncToolCalls(toolCallIds: string[]) {
637646
if (toolCallIds.length === 0) return []
638-
return withDbSpan(
647+
return await withDbSpan(
639648
TraceSpan.CopilotAsyncRunsGetMany,
640649
'SELECT',
641650
'copilot_async_tool_calls',
@@ -649,7 +658,7 @@ export async function getAsyncToolCalls(toolCallIds: string[]) {
649658
}
650659

651660
export async function claimCompletedAsyncToolCall(toolCallId: string, workerId: string) {
652-
return withDbSpan(
661+
return await withDbSpan(
653662
TraceSpan.CopilotAsyncRunsClaimCompleted,
654663
'UPDATE',
655664
'copilot_async_tool_calls',
@@ -679,7 +688,7 @@ export async function claimCompletedAsyncToolCall(toolCallId: string, workerId:
679688
}
680689

681690
async function releaseCompletedAsyncToolClaim(toolCallId: string, workerId: string) {
682-
return withDbSpan(
691+
return await withDbSpan(
683692
TraceSpan.CopilotAsyncRunsReleaseClaim,
684693
'UPDATE',
685694
'copilot_async_tool_calls',

apps/sim/next.config.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,16 @@ const nextConfig: NextConfig = {
219219
* it lives. Restoring across commits is separately undocumented-as-supported
220220
* (vercel/next.js#87283 reports stale HTML from a cache built elsewhere).
221221
*
222-
* The explicit pin is load-bearing: 16.3.0 flipped this default to true for
223-
* stable (vercel/next.js#94616), so dropping it re-enables the slower cache.
222+
* Keep the explicit pin even while we sit on 16.2.12: 16.3.0 flips this
223+
* default to true for stable (vercel/next.js#94616), so dropping it would
224+
* silently re-enable the slower cache the next time we take that bump.
224225
*/
225226
turbopackFileSystemCacheForBuild: false,
226227
/**
227228
* TypeScript 7 ships no JavaScript compiler API until 7.1, so Next's default
228229
* checker cannot load it — this shells out to the project-local `tsc` instead.
229230
* Pinned because the failure mode is not slower type checking but none at all:
230-
* 16.2.12 skipped the stage silently in 138ms.
231+
* without it 16.2.12 skips the stage silently in 138ms.
231232
*/
232233
useTypeScriptCli: true,
233234
preloadEntriesOnStart: false,

apps/sim/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@
193193
"mongodb": "6.19.0",
194194
"mysql2": "3.14.3",
195195
"neo4j-driver": "6.0.1",
196-
"next": "16.3.0",
196+
"next": "16.2.12",
197197
"next-mdx-remote": "^6.0.0",
198198
"next-runtime-env": "3.3.0",
199199
"next-themes": "^0.4.6",

0 commit comments

Comments
 (0)