fix(process): name the Swift compile deadline (#6071)

Name REA_SWIFTC_COMPILE_TIMEOUT_MS, its effective milliseconds, the
1..2147483647 range, and the omitted 120000 ms default when a Darwin
ownership-helper compile times out. A change applies after REA restarts,
on the next ownership-helper compile.

Producer SIGXCPU and SIGXFSZ recovery says REA retains the tighter
inherited limit. Agent-visible fixed caps in this theme say this release
has no runtime override. Numbers are unchanged.

Refs #5910

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-10-21 23:46:29 +08:00
1 parent f86206535f
commit d8a9673fc4
26 files changed
+321 -95

No files matched your search

+75 -1
View File
@@ -491,6 +491,77 @@ it.each([false, true])(
},
);
it("names the fixed producer command deadline and says it has no override", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_packet_fields",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const failure = new OwnedCommandFailure("timeout", "command timed out", null);
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 30_000, output_bytes: 1024 },
undefined,
);
const projected = projectAnalysisError(error);
expect(projected.message).toContain("30000 ms");
expect(projected.remediation.action).toContain(
"this release has no runtime override",
);
expect(projected.remediation.action).not.toContain(
"REA_SWIFTC_COMPILE_TIMEOUT_MS",
);
} finally {
await resources.close();
}
});
it("says REA retains the tighter inherited limit when a producer signal fires", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_packet_fields",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const empty = { text: "", bytes: 0, observedBytes: 0 };
const failure = new OwnedCommandFailure("process", "producer signal", {
stdout: empty,
stderr: empty,
exitCode: null,
signal: "SIGXCPU",
});
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 30_000, output_bytes: 4_194_304 },
{
address_space_bytes: 1_073_741_824,
file_bytes: 67_108_864,
cpu_seconds: 30,
},
);
const projected = projectAnalysisError(error);
expect(projected.remediation.action).toContain(
"REA retains the tighter inherited limit",
);
expect(projected.remediation.action).toContain("1073741824 bytes");
expect(projected.remediation.action).toContain("67108864 bytes");
expect(projected.remediation.action).toContain("30 CPU seconds");
expect(projected.remediation.action).toContain(
"This release has no runtime override",
);
expect(projected.details).toMatchObject({
reported_limits: { cpu_seconds: 30 },
});
} finally {
await resources.close();
}
});
it("treats a fixed command-output cap as a non-retryable capture allowance", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
@@ -519,7 +590,10 @@ it("treats a fixed command-output cap as a non-retryable capture allowance", asy
const projected = projectAnalysisError(error);
expect(projected.retryable).toBe(false);
expect(projected.remediation.action).toContain(
"The retained command-output allowance",
"The retained command-output allowance of 1024 bytes",
);
expect(projected.remediation.action).toContain(
"this release has no runtime override",
);
expect(projected.remediation.action).not.toMatch(
/Review the reported worker memory limits/u,
+19 -10
View File
@@ -352,11 +352,11 @@ export class ArtifactAnalysisResources {
cause.cancellation === undefined ? cause : cause.cancellation.reason,
});
if (cause.reason === "timeout")
return new AnalysisTimeoutError(
operation,
limits.command_timeout_ms,
detail,
);
return new AnalysisTimeoutError(operation, limits.command_timeout_ms, {
...detail,
userMessage: `The owned producer command reached its fixed ${String(limits.command_timeout_ms)} ms deadline. This is not a client-side timeout; extending only the MCP client's deadline does not change this budget.`,
remediationAction: `REA's fixed producer command deadline is ${String(limits.command_timeout_ms)} ms and this release has no runtime override. Inspect any partial output and cleanup before retrying. Repeating the unchanged request reaches the same deadline.`,
});
if (cause.reason === "output-limit")
return new AnalysisResourceConstraintError(
operation,
@@ -366,7 +366,7 @@ export class ArtifactAnalysisResources {
{
...detail,
...fixedAllowanceOptions(
"The retained command-output allowance",
`The retained command-output allowance of ${String(limits.output_bytes)} bytes (this release has no runtime override)`,
"capture",
),
},
@@ -375,14 +375,23 @@ export class ArtifactAnalysisResources {
cause.reason === "process" &&
(cause.snapshot?.signal === "SIGXFSZ" ||
cause.snapshot?.signal === "SIGXCPU")
)
) {
const signal = cause.snapshot.signal;
const reported =
effectiveLimits === undefined
? "The effective inherited limit was not reported."
: `The effective inherited limits are ${String(effectiveLimits.address_space_bytes)} bytes of address space, ${String(effectiveLimits.file_bytes)} bytes per file, and ${String(effectiveLimits.cpu_seconds)} CPU seconds.`;
return new AnalysisResourceConstraintError(
operation,
cause.snapshot.signal === "SIGXFSZ" ? "file-size" : "cpu",
`Producer terminated with ${cause.snapshot.signal}; the signal's exact cause is not independently established.`,
signal === "SIGXFSZ" ? "file-size" : "cpu",
`Producer terminated with ${signal}; the signal's exact cause is not independently established.`,
effectiveLimits === undefined ? null : { ...effectiveLimits },
detail,
{
...detail,
remediationAction: `${reported} REA retains the tighter inherited limit. This release has no runtime override for that ceiling. A smaller artifact may finish inside the retained limit. Repeating the unchanged request fails the same way.`,
},
);
}
return new AnalysisOutputError(operation, cause.message, detail);
}
@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import { resolveSwiftcCompileTimeoutMs } from "./DarwinProcessRunTokenReader.js";
import {
resolveSwiftcCompileTimeoutMs,
swiftcCompileTimeoutRecovery,
} from "./DarwinProcessRunTokenReader.js";
import { MAX_PROVIDER_STARTUP_TIMEOUT_MS } from "../config/providerStartupTimeout.js";
describe("resolveSwiftcCompileTimeoutMs", () => {
it("keeps the historical default when unset", () => {
@@ -31,4 +35,14 @@ describe("resolveSwiftcCompileTimeoutMs", () => {
}),
).toBe(120_000);
});
it("names the launch-environment deadline, range, and restart on a timeout", () => {
const recovery = swiftcCompileTimeoutRecovery(300_000);
expect(recovery).toContain("REA_SWIFTC_COMPILE_TIMEOUT_MS");
expect(recovery).toContain("300000 ms");
expect(recovery).toContain(`1..${String(MAX_PROVIDER_STARTUP_TIMEOUT_MS)}`);
expect(recovery).toContain("120000 ms");
expect(recovery).toContain("restart REA");
expect(recovery).toContain("next ownership-helper compile");
});
});
+19 -4
View File
@@ -7,6 +7,7 @@ import { z } from "zod";
import {
DEFAULT_SWIFTC_COMPILE_TIMEOUT_MS,
MAX_PROVIDER_STARTUP_TIMEOUT_MS,
providerStartupTimeoutSchema,
} from "../config/providerStartupTimeout.js";
import { err, ok, type Result } from "../domain/result.js";
@@ -105,7 +106,9 @@ export const SWIFTC_INTERRUPT = "SIGINT";
* Swift SDK cache on the supported Intel macOS VM (about 88s), so compilation
* stays bounded independently of short provider version probes. Invalid ambient
* values fall back to the default; `parseConfig` still rejects them at CLI/MCP
* admission.
* admission. The resolved value is captured when the reader is created.
* Production creates that reader from the launch environment at process start,
* so a change applies after REA restarts, on the next ownership-helper compile.
*/
export const resolveSwiftcCompileTimeoutMs = (
options: {
@@ -121,6 +124,10 @@ export const resolveSwiftcCompileTimeoutMs = (
return parsed.success ? parsed.data : DEFAULT_SWIFTC_COMPILE_TIMEOUT_MS;
};
/** Name the existing swiftc deadline when a helper compile times out. */
export const swiftcCompileTimeoutRecovery = (timeoutMs: number): string =>
`The effective REA_SWIFTC_COMPILE_TIMEOUT_MS value is ${String(timeoutMs)} ms (integer range 1..${String(MAX_PROVIDER_STARTUP_TIMEOUT_MS)}; omitted default ${String(DEFAULT_SWIFTC_COMPILE_TIMEOUT_MS)} ms). Set it in the environment that starts REA and restart REA; the next ownership-helper compile uses the new value.`;
/** Bound on compiler diagnostics embedded in the inspection failure text. */
const STDERR_EXCERPT_LIMIT = 512;
@@ -272,8 +279,11 @@ const compilerOutcomeDescription = (
if (outcome === undefined) return `compiler result: ${String(fallbackCode)}`;
if (outcome.outcome === "exited")
return `compiler result: exit code ${String(outcome.code)}`;
if (outcome.outcome === "timed_out")
return `compiler result: timed out after ${String(outcome.timeoutMs)} ms`;
if (outcome.outcome === "timed_out") {
if (outcome.timeoutMs === undefined)
return "compiler result: timed out. The effective REA_SWIFTC_COMPILE_TIMEOUT_MS value was not reported.";
return `compiler result: timed out after ${String(outcome.timeoutMs)} ms. ${swiftcCompileTimeoutRecovery(outcome.timeoutMs)}`;
}
if (outcome.outcome === "signaled")
return `compiler result: stopped by signal ${outcome.signal ?? "an unknown signal"}`;
return `compiler result: ${String(outcome.code)}`;
@@ -302,8 +312,13 @@ const compilerInspectionError = (
stderr.truncated ||
outcome?.stderrTruncated === true ||
outcome?.outputTruncated === true;
const truncationNote = !truncated
? ""
: stderr.truncated
? `… [diagnostics truncated at ${String(STDERR_EXCERPT_LIMIT)} bytes; this release has no runtime override]`
: "… [diagnostics truncated; this release has no runtime override]";
const suffix = diagnostics
? `: ${diagnostics}${truncated ? "… [diagnostics truncated]" : ""}`
? `: ${diagnostics}${truncationNote}`
: outcome === undefined
? `: ${cause instanceof Error ? cause.message : String(cause)}`
: ": no compiler diagnostics were captured";
+10 -11
View File
@@ -328,6 +328,13 @@ const mergeUnverifiedObservations = (
return { ...result, unverified: merged };
};
/**
* Times one group may be revalidated in a single cleanup. The second pass
* covers a group that reappears after it was already processed. Another
* appearance stays unresolved instead of reporting success.
*/
const OWNED_GROUP_REVALIDATION_LIMIT = 2;
/** Token-validate every rooted POSIX process group before signaling any. */
export const cleanupOwnedProcessGroup = async (
ownership: OwnedProcessGroup,
@@ -360,7 +367,7 @@ export const cleanupOwnedProcessGroup = async (
return mergeUnverifiedObservations(
{
cleaned: false,
reason: `owned process group ${String(processGroupId)} reappeared after revalidation`,
reason: `owned process group ${String(processGroupId)} reappeared after ${String(OWNED_GROUP_REVALIDATION_LIMIT)} revalidation passes. This release has no runtime override.`,
},
plan.unverified,
);
@@ -380,7 +387,7 @@ export const cleanupOwnedProcessGroup = async (
return mergeUnverifiedObservations(
{
cleaned: false,
reason: `owned process group ${String(groupId)} reappeared after revalidation`,
reason: `owned process group ${String(groupId)} reappeared after ${String(OWNED_GROUP_REVALIDATION_LIMIT)} revalidation passes. This release has no runtime override.`,
},
plan.unverified,
);
@@ -631,13 +638,6 @@ const isExitRefreshCandidate = (
);
};
/**
* Times one group may be revalidated in a single cleanup. The second pass
* covers a group that reappears after it was already processed. Another
* appearance stays unresolved instead of reporting success.
*/
const OWNED_GROUP_REVALIDATION_LIMIT = 2;
/**
* Token-check rows that exist only in the post-exit table. A prior PID, group,
* or ancestry link is handled by the caller; these rows have none of those.
@@ -716,8 +716,7 @@ const classifyUnlinkedExitRows = async (
if (confirmations > EXIT_REFRESH_CONFIRMATION_LIMIT)
return {
kind: "uncertain",
reason:
"process ownership could not be revalidated: exit refresh confirmation limit exceeded",
reason: `process ownership could not be revalidated: the exit refresh confirmation limit of ${String(EXIT_REFRESH_CONFIRMATION_LIMIT)} reads was exceeded. This release has no runtime override.`,
};
// The confirmation read is its own table. A PID classified on the previous
// table, including a readable foreign token, does not identify the process
@@ -592,7 +592,7 @@ describe("identityless exit refresh keeps later confirmation rows", () => {
).resolves.toMatchObject({
cleaned: false,
reason:
"process ownership could not be revalidated: exit refresh confirmation limit exceeded",
"process ownership could not be revalidated: the exit refresh confirmation limit of 8 reads was exceeded. This release has no runtime override.",
});
expect(signalGroup).not.toHaveBeenCalled();
});
@@ -875,7 +875,8 @@ describe("identityless cleanup revalidates a group that reappears", () => {
),
).resolves.toMatchObject({
cleaned: false,
reason: "owned process group 101 reappeared after revalidation",
reason:
"owned process group 101 reappeared after 2 revalidation passes. This release has no runtime override.",
});
expect(signalGroup.mock.calls).toEqual([[100, "SIGKILL"]]);
});
+33 -23
View File
@@ -14,7 +14,37 @@ import { err, ok, type Result } from "../domain/result.js";
const MATCHING_DEADLINE_MS = 5_000;
const WORKER_HEAP_MIB = 128;
/** Shared capability text for the supervised ECMAScript matching boundary. */
export const REGEX_MATCHING_LIMITATION = `Regex mode uses ECMAScript Unicode semantics in a cancellable worker with a ${String(MATCHING_DEADLINE_MS / 1000)}-second matching deadline; matching failure leaves the service available.`;
export const REGEX_MATCHING_LIMITATION = `Regex mode uses ECMAScript Unicode semantics in a cancellable worker with a ${String(MATCHING_DEADLINE_MS / 1000)}-second matching deadline and a ${String(WORKER_HEAP_MIB)} MiB worker heap. This release has no runtime override for either cap; matching failure leaves the service available.`;
/** Fixed regex deadline. The worker heap and this deadline are separate caps. */
export const regexMatchingDeadlineError = (
operation: string,
): AnalysisResourceConstraintError =>
new AnalysisResourceConstraintError(
operation,
"cpu",
`Regex matching exceeded its supervised deadline of ${String(MATCHING_DEADLINE_MS)} ms. This release has no runtime override. The service remains available.`,
{ matching_deadline_ms: MATCHING_DEADLINE_MS },
{
remediationAction: `The regex matching deadline is ${String(MATCHING_DEADLINE_MS)} ms and this release has no runtime override. Simplify the ECMAScript Unicode regex or use literal mode, then retry in the same session.`,
},
);
/** Fixed regex worker heap. Inherited limits and host memory do not raise it. */
export const regexWorkerHeapError = (
operation: string,
cause: Error,
): AnalysisResourceConstraintError =>
new AnalysisResourceConstraintError(
operation,
"memory",
`Regex worker exhausted its ${String(WORKER_HEAP_MIB)} MiB heap budget. This release has no runtime override.`,
{ worker_heap_mib: WORKER_HEAP_MIB },
{
cause,
remediationAction: `The regex worker heap is ${String(WORKER_HEAP_MIB)} MiB and this release has no runtime override. Inherited limits and additional host memory do not raise it. Simplify the pattern or use literal mode.`,
},
);
const replySchema = z.discriminatedUnion("status", [
z.strictObject({
status: z.literal("matched"),
@@ -84,21 +114,7 @@ export const matchRegexValues = async (input: {
const onAbort = () =>
finish(err(new AnalysisCancelledError(input.operation)));
const timer = setTimeout(
() =>
finish(
err(
new AnalysisResourceConstraintError(
input.operation,
"cpu",
"Regex matching exceeded its supervised deadline; the service remains available.",
{ matching_deadline_ms: MATCHING_DEADLINE_MS },
{
remediationAction:
"Simplify the ECMAScript Unicode regex or use literal mode, then retry in the same session.",
},
),
),
),
() => finish(err(regexMatchingDeadlineError(input.operation))),
MATCHING_DEADLINE_MS,
);
input.signal?.addEventListener("abort", onAbort, { once: true });
@@ -144,13 +160,7 @@ export const matchRegexValues = async (input: {
finish(
err(
cause.code === "ERR_WORKER_OUT_OF_MEMORY"
? new AnalysisResourceConstraintError(
input.operation,
"memory",
"Regex worker exhausted its heap budget",
{ worker_heap_mib: WORKER_HEAP_MIB },
{ cause },
)
? regexWorkerHeapError(input.operation, cause)
: new AnalysisOutputError(
input.operation,
`Regex worker failed: ${cause.message}`,
+1 -1
View File
@@ -233,7 +233,7 @@ export const stopOwnedDebuggerProcess = async (
if (detached === undefined)
return {
status: "incomplete",
reason: `Owned ${debuggerName} inferior detach was not verified within ${DETACH_TIMEOUT_MS} ms.`,
reason: `Owned ${debuggerName} inferior detach was not verified within ${String(DETACH_TIMEOUT_MS)} ms. This release has no runtime override.`,
};
if (!isVerifiedDetachResult(detached)) {
detachOutcomes.set(session, "unconfirmed");