Skip to content

Commit 8ac4255

Browse files
committed
fix(api): qualify run recovery and trace duration guidance
1 parent 1b624da commit 8ac4255

6 files changed

Lines changed: 19 additions & 16 deletions

File tree

‎.agents/skills/v2-api-conventions/SKILL.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,11 @@ Audited against the primary specs and against Stripe, GitHub, and Google's AIPs.
197197
`POST /workflows/{id}/execute` accepts `X-Run-Id` from API-key and OAuth callers; anonymous requests ignore it. It is a **uniqueness claim, not an idempotency key**:
198198

199199
- An available ID is claimed before execution starts.
200-
- An already claimed ID returns **409** with `error.details.code: "RUN_ID_CONFLICT"`, the run id in `error.details.runId`, and an `X-Run-Id` response header. It never replays the earlier run's result — the client recovers it by polling the runs resource.
200+
- An already claimed ID returns **409** with `error.details.code: "RUN_ID_CONFLICT"`, the run id in `error.details.runId`, and an `X-Run-Id` response header. It never replays the earlier run's result. Get Workflow Run can retrieve an existing run, but a claim does not guarantee a retrievable run.
201201
- IDs of runs that started remain reserved after their execution logs are deleted.
202+
- An ambiguous enqueue can retain the claim indefinitely without creating a retrievable run. A **409** followed by **404** is an unresolved outcome, not proof that execution never started or that the ID will become reusable.
202203

203-
For an uncertain execution outcome, retry with the same run ID or poll Get Workflow Run. A fresh ID or an omitted header can start another execution; never describe either as a safe retry of the original request. Failures before a run starts can release the claim, so phrase the conflict rule as an ID that is already claimed.
204+
For an uncertain execution outcome, reuse the same run ID if retrying and check Get Workflow Run. Do not promise polling will eventually find a run. If the outcome cannot be verified, do not automatically restart with a fresh ID or an omitted header: either can start another execution. Failures before a run starts can release the claim, so phrase the conflict rule as an ID that is already claimed.
204205

205206
That makes the money path safe against double-execution **for callers that opt in**. What it is not: a Stripe-style `Idempotency-Key` that stores and replays the original status and body. Building that means a request fingerprint, a retention window, an in-flight-vs-completed distinction (the expired IETF draft would have these be 422 and 409 respectively), and somewhere to put a large synchronous execution body. It is a designed piece of work, not an increment — do not half-build it by aliasing the header name, which would invite clients written against Stripe semantics to treat our 409 as a hard failure.
206207

‎apps/docs/openapi-v2-logs.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,11 +1109,11 @@
11091109
},
11101110
"duration": {
11111111
"type": "number",
1112-
"description": "Span duration in milliseconds."
1112+
"description": "Current trace-span duration in milliseconds."
11131113
},
11141114
"durationMs": {
11151115
"type": "number",
1116-
"description": "Span duration in milliseconds."
1116+
"description": "Compatibility field for span duration in milliseconds. Read `duration` for current trace spans."
11171117
},
11181118
"startTime": {
11191119
"type": "string",

‎apps/docs/openapi-v2-workflows.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,9 +2532,9 @@
25322532
"name": "x-run-id",
25332533
"in": "header",
25342534
"required": false,
2535-
"description": "Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it when retrying an uncertain request. An already claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying the result; use Get Workflow Run to check progress. A fresh ID or an omitted header can start another run.",
2535+
"description": "Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it after an uncertain response: a claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying results. Check Get Workflow Run, but `404` can persist while the ID remains claimed and does not establish whether execution started. Do not automatically restart with a fresh or omitted ID; either can start another run.",
25362536
"schema": {
2537-
"description": "Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it when retrying an uncertain request. An already claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying the result; use Get Workflow Run to check progress. A fresh ID or an omitted header can start another run.",
2537+
"description": "Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it after an uncertain response: a claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying results. Check Get Workflow Run, but `404` can persist while the ID remains claimed and does not establish whether execution started. Do not automatically restart with a fresh or omitted ID; either can start another run.",
25382538
"type": "string",
25392539
"minLength": 1,
25402540
"maxLength": 128,

‎apps/sim/lib/api/contracts/logs.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,13 @@ export const traceSpanSchema: z.ZodType<LogTraceSpan> = z
218218
id: z.string().describe('Trace-span identifier.'),
219219
name: z.string().describe('Trace-span name.'),
220220
type: z.string().describe('Trace-span category.'),
221-
duration: z.number().describe('Span duration in milliseconds.').optional(),
222-
durationMs: z.number().describe('Span duration in milliseconds.').optional(),
221+
duration: z.number().describe('Current trace-span duration in milliseconds.').optional(),
222+
durationMs: z
223+
.number()
224+
.describe(
225+
'Compatibility field for span duration in milliseconds. Read `duration` for current trace spans.'
226+
)
227+
.optional(),
223228
startTime: z.string().describe('ISO 8601 span start timestamp.').optional(),
224229
endTime: z.string().describe('ISO 8601 span end timestamp.').optional(),
225230
status: z.string().describe('Trace-span status.').optional(),

‎apps/sim/lib/api/contracts/v2/workflows.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,12 @@ export const v2WorkflowRunIdSchema = runIdSchema
6969
.meta({ examples: ['run_8f14e45f-ceea-467f-a'] })
7070

7171
/**
72-
* `X-Run-Id` is a **one-shot uniqueness claim, not an idempotency key.** The
73-
* first request to claim a value starts a run; every later request reusing it
74-
* is rejected with a `409` carrying `error.details.code: "RUN_ID_CONFLICT"`, and
75-
* the original result is never
76-
* replayed. Retry logic written against idempotency-key semantics either
77-
* double-executes (fresh id per attempt) or hard-fails (same id per attempt).
72+
* `X-Run-Id` reserves an execution ID without guaranteeing a retrievable run.
73+
* Claims can survive uncertain execution outcomes; a missing run does not make
74+
* a claimed ID reusable. Reusing a claimed ID returns a conflict, never a replay.
7875
*/
7976
const X_RUN_ID_DESCRIPTION =
80-
'Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it when retrying an uncertain request. An already claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying the result; use Get Workflow Run to check progress. A fresh ID or an omitted header can start another run.'
77+
'Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it after an uncertain response: a claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying results. Check Get Workflow Run, but `404` can persist while the ID remains claimed and does not establish whether execution started. Do not automatically restart with a fresh or omitted ID; either can start another run.'
8178

8279
const X_SIM_VIA_DESCRIPTION =
8380
'Comma-separated workflow identifiers naming the workflow-to-workflow call chain that led to this request. Each hop appends its own workflow id, and Sim sets it automatically; supply it yourself only when relaying an existing chain. A chain at the maximum depth is rejected with `409` and `error.details.code: "CALL_CHAIN_DEPTH_EXCEEDED"`.'

‎packages/sim-cli/src/generated/v2-api.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11418,7 +11418,7 @@ export const V2_OPERATIONS = {
1141811418
'x-run-id': {
1141911419
kind: 'string',
1142011420
describe:
11421-
'Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it when retrying an uncertain request. An already claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying the result; use Get Workflow Run to check progress. A fresh ID or an omitted header can start another run.',
11421+
'Run ID for API-key or OAuth callers; ignored for anonymous requests. Reuse it after an uncertain response: a claimed ID returns `409` with `RUN_ID_CONFLICT`, without replaying results. Check Get Workflow Run, but `404` can persist while the ID remains claimed and does not establish whether execution started. Do not automatically restart with a fresh or omitted ID; either can start another run.',
1142211422
},
1142311423
'x-sim-via': {
1142411424
kind: 'string',

0 commit comments

Comments
 (0)