Repository navigation
Cloud: stop deterministic provider failure storms #12420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,19 @@ export async function POST( | |
| details: { field: "command" }, | ||
| }); | ||
| } | ||
| const commandBytes = Buffer.byteLength(command, "utf8"); | ||
| const MAX_PROVIDER_COMMAND_BYTES = 64 * 1024; | ||
| if (commandBytes > MAX_PROVIDER_COMMAND_BYTES) { | ||
| return vmErrorResponse({ | ||
| error: "vm_command_too_large", | ||
| status: 413, | ||
| message: `Cloud VM commands must be 64 KiB or smaller. This command is ${commandBytes} bytes.`, | ||
| action: "Split the command into smaller requests or upload a script and execute the script path.", | ||
|
Comment on lines
+64
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Localize the new
🤖 Prompt for AI Agents |
||
| phase: "exec", | ||
| retryable: false, | ||
| details: { commandBytes, maxCommandBytes: MAX_PROVIDER_COMMAND_BYTES }, | ||
| }); | ||
| } | ||
| // Clamp the timeout so a client can't tie up provider quota on a runaway exec. Upper | ||
| // bound matches the provider defaults (15 min on Freestyle); negative / non-number | ||
| // values fall back to 30s. | ||
|
|
@@ -69,7 +82,7 @@ export async function POST( | |
| if (!account.ok) return account.response; | ||
| setSpanAttributes(span, { | ||
| "cmux.vm.id": id, | ||
| "cmux.command_length": command.length, | ||
| "cmux.command_length": commandBytes, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use UTF-8 bytes for the usage event too. The route passes the trimmed command unchanged to Proposed alignment- metadata: { commandLength: input.command.length, exitCode: result.exitCode },
+ metadata: { commandLength: Buffer.byteLength(input.command, "utf8"), exitCode: result.exitCode },🤖 Prompt for AI Agents |
||
| "cmux.timeout_ms": timeoutMs, | ||
| }); | ||
| const run = await runVmRoute(execVm({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,7 @@ const transports = new Set(["unknown", "iroh", "tailscale", "websocket", "debugL | |
|
|
||
| const allowedPropertyKeys = new Set([ | ||
| "phase", "outcome", "duration_ms", "runtime_role", "user_usable", | ||
| "failure", "transport", "platform", "app_version", "build_number", | ||
| "failure", "transport", "platform", "client_channel", "app_version", "build_number", | ||
| "bundle_identifier", "os_version", "device_model", | ||
| ]); | ||
|
|
||
|
|
@@ -44,6 +44,7 @@ export type MobileNetworkOutcome = { | |
| readonly failure?: string; | ||
| readonly transport?: string; | ||
| readonly platform?: "ios"; | ||
| readonly clientChannel?: "dev" | "nightly" | "production" | "unknown"; | ||
| readonly appVersion?: string; | ||
| readonly buildNumber?: string; | ||
| readonly bundleIdentifier?: string; | ||
|
|
@@ -67,7 +68,7 @@ export function parseMobileNetworkOutcome(candidate: unknown): MobileNetworkOutc | |
| } | ||
|
|
||
| type CoreObservation = Pick<MobileNetworkOutcome, "phase" | "outcome" | "durationMs" | "userUsable" | "failure" | "transport">; | ||
| type Metadata = Pick<MobileNetworkOutcome, "platform" | "appVersion" | "buildNumber" | "bundleIdentifier" | "osVersion" | "deviceModel">; | ||
| type Metadata = Pick<MobileNetworkOutcome, "platform" | "clientChannel" | "appVersion" | "buildNumber" | "bundleIdentifier" | "osVersion" | "deviceModel">; | ||
|
|
||
| function validTimestamp(value: unknown): value is string { | ||
| return typeof value === "string" | ||
|
|
@@ -100,14 +101,16 @@ function parseCore(properties: Record<string, unknown>): CoreObservation | null | |
|
|
||
| function parseMetadata(properties: Record<string, unknown>): Metadata | null { | ||
| const platform = optionalExact(properties.platform, "ios"); | ||
| const clientChannel = optionalSetValue(properties.client_channel, new Set(["dev", "nightly", "production", "unknown"])); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- package scripts ---'
sed -n '1,220p' package.json
printf '%s\n' '--- target file ---'
sed -n '1,180p' web/services/observability/mobileNetworkOutcome.ts
printf '%s\n' '--- helper and type bindings ---'
rg -n -C 4 'optionalSetValue|type Metadata|interface Metadata|clientChannel' webRepository: manaflow-ai/cmux Length of output: 18066 🤖 get_repo_knowledge executed:
Length of output: 47595 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- web package scripts ---'
if [ -f web/package.json ]; then sed -n '1,220p' web/package.json; fi
printf '%s\n' '--- TypeScript configuration files ---'
fd -H -t f '(^|/)(tsconfig[^/]*\.json|biome\.jsonc?|package\.json)$' . | sort
printf '%s\n' '--- declared type-check commands and compiler references ---'
rg -n -i 'typecheck|type-check|tsc|typescript|noImplicit|strict' --glob 'package.json' --glob 'tsconfig*.json' --glob 'biome*.json*' --glob '!node_modules/**' .Repository: manaflow-ai/cmux Length of output: 10637 🏁 Script executed: #!/bin/bash
set -o pipefail
cd web
bun run typecheck
status=$?
printf '\n[typecheck_exit_status=%s]\n' "$status"
exit "$status"Repository: manaflow-ai/cmux Length of output: 270 Preserve the literal type for
🤖 Prompt for AI Agents |
||
| const appVersion = optionalMachineString(properties.app_version); | ||
| const buildNumber = optionalMachineString(properties.build_number); | ||
| const bundleIdentifier = optionalMachineString(properties.bundle_identifier); | ||
| const osVersion = optionalMachineString(properties.os_version); | ||
| const deviceModel = optionalMachineString(properties.device_model, true); | ||
| if ([platform, appVersion, buildNumber, bundleIdentifier, osVersion, deviceModel].includes(false)) return null; | ||
| if ([platform, clientChannel, appVersion, buildNumber, bundleIdentifier, osVersion, deviceModel].includes(false)) return null; | ||
| return { | ||
| ...(platform === "ios" ? { platform } : {}), | ||
| ...(typeof clientChannel === "string" ? { clientChannel } : {}), | ||
| ...(typeof appVersion === "string" ? { appVersion } : {}), | ||
| ...(typeof buildNumber === "string" ? { buildNumber } : {}), | ||
| ...(typeof bundleIdentifier === "string" ? { bundleIdentifier } : {}), | ||
|
|
@@ -136,6 +139,7 @@ export async function emitMobileNetworkOutcomes( | |
| "cmux.mobile.failure": observation.failure, | ||
| "cmux.mobile.transport": observation.transport, | ||
| "cmux.mobile.platform": observation.platform, | ||
| "cmux.client.channel": observation.clientChannel, | ||
| "cmux.mobile.app_version": observation.appVersion, | ||
| "cmux.mobile.build_number": observation.buildNumber, | ||
| "cmux.mobile.bundle_identifier": observation.bundleIdentifier, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -383,7 +383,9 @@ export function reconcileVmProviderStatuses(input: { | |||||
| ensureNetwork(owner.provider, { slug: networkSlugForUser(owner.userId), heal: true }).pipe( | ||||||
| Effect.catchAll(() => Effect.void), | ||||||
| ), | ||||||
| { concurrency: 4, discard: true }, | ||||||
| // Freestyle returns 429 when several VPC rule heals run together. | ||||||
| // One owner at a time keeps healing bounded. | ||||||
| { concurrency: 1, discard: true }, | ||||||
| ); | ||||||
| } | ||||||
| let updated = 0; | ||||||
|
|
@@ -2822,6 +2824,7 @@ export function getVmStats(input: { | |||||
| readonly providerVmId: string; | ||||||
| }) { | ||||||
| return Effect.gen(function* () { | ||||||
| const repo = yield* VmRepository; | ||||||
| const providers = yield* VmProviderGateway; | ||||||
| const vm = yield* requireUserVm(input); | ||||||
| // No resume preflight on purpose: a reading must never wake a sleeping machine. | ||||||
|
|
@@ -2834,7 +2837,19 @@ export function getVmStats(input: { | |||||
| }), | ||||||
| ); | ||||||
| } | ||||||
| return yield* providers.getStats(vm.provider, input.providerVmId); | ||||||
| return yield* providers.getStats(vm.provider, input.providerVmId).pipe( | ||||||
| Effect.catchAll((error) => { | ||||||
| if (!isProviderNotFoundError(error)) return Effect.fail(error); | ||||||
| return Effect.gen(function* () { | ||||||
| yield* repo.markProviderObservedStatus({ | ||||||
| id: vm.id, | ||||||
| providerVmId: input.providerVmId, | ||||||
| status: "destroyed", | ||||||
| }).pipe(Effect.catchAll(() => Effect.succeed(false))); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Do not suppress the destroyed-state write failure. When Proposed fix- }).pipe(Effect.catchAll(() => Effect.succeed(false)));
+ });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| return yield* Effect.fail(new VmNotFoundError({ vmId: input.providerVmId })); | ||||||
| }); | ||||||
| }), | ||||||
| ); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: manaflow-ai/cmux
Length of output: 46290
🤖 get_repo_knowledge executed:
get_repo_knowledge manaflow-ai/cmux /tmp/coderabbit-repo-knowledge/manaflow-ai-cmux-b0f68d40/architecture /tmp/coderabbit-repo-knowledge/manaflow-ai-cmux-b0f68d40/conventions /tmp/coderabbit-repo-knowledge/manaflow-ai-cmux-b0f68d40/learningsLength of output: 47639
🏁 Script executed:
Repository: manaflow-ai/cmux
Length of output: 50377
🏁 Script executed:
Repository: manaflow-ai/cmux
Length of output: 6202
Do not classify development bundles as production.
ios/scripts/reload.shcreates tagged development bundles asdev.cmux.ios.<tag-slug>, including thedev.cmux.ios.axnetfixture. Because these identifiers do not contain"debug", the fallback recordsclient_channelas"production". Use an explicit build channel, or map unmatched identifiers to"unknown".🤖 Prompt for AI Agents
Source: Path instructions