Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Backport #2011: docs: document run idempotency by github-actions[bot] · Pull Request #2410 · vercel/workflow · GitHub
Skip to content

Backport #2011: docs: document run idempotency - #2410

Closed
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable
Closed

Backport #2011: docs: document run idempotency#2410
github-actions[bot] wants to merge 1 commit into
stablefrom
backport/pr-2011-to-stable

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated backport of #2011 to stable (backport job run).

AI recommendation: This is a documentation-only change (everything under docs/content/, which is maintained on stable) that improves idempotency docs whose content already exists on stable and documents the hook.getConflict() API, which I verified is already present on stable (docs/content/docs/foundations/hooks.mdx). stable keeps the docs in a flat docs/content/docs/foundations/... layout rather than main's v4/v5 split, so the cherry-pick will need path/conflict resolution, but the change is not main-only and a human reviews the resulting PR.

Merge conflicts were resolved by AI (opencode with anthropic/claude-opus-4.8). Please review the conflict resolution carefully before merging.

* docs: document run idempotency
* docs: address idempotency review feedback
* docs: make hook tokens the idempotency pattern
* docs: address toolbar idempotency feedback
* docs: clarify idempotency page description
* docs: scope idempotency descriptions
* docs: move step idempotency example under section
* docs: simplify idempotency guidance
* docs: simplify idempotency cookbook
* docs: add empty changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
* docs: address idempotency review feedback
* feat: add hook ready promise
* docs: mention conflicting hook run id
* test: cover hook ready continuation scheduling
* feat: replace hook.ready with hook.hasConflict (Promise<boolean>)
- hook.hasConflict resolves true when the token is owned by another
active hook, false once registration is committed — no throw, so
workflows can branch on conflicts early. Awaiting it suspends the
workflow to commit the hook registration (createHook alone does not).
- Chain the already-created fast-path through promiseQueue so
resolution order matches event-log order (review feedback).
- Skip inline step execution when a suspension has an awaited hook
creation so the hasConflict continuation can advance independently
of step execution (review feedback).
- Update unit tests, e2e tests, workbench workflows, and v4/v5 docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fix inconsistent hasConflict bullet in create-webhook reference
State both resolution values explicitly (true = token already owned,
false = registered) instead of a parenthetical that only described the
false case.
* docs: require docs preview links in PR descriptions for docs changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore SWC Plugin heading in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.hasConflict in run idempotency docs
- Primary claim pattern is now `if (await hook.hasConflict)` instead of
try/catch on HookConflictError; payload awaits still reject with
HookConflictError (with conflictingRunId) when the owner's run ID is
needed.
- Route example returns the active owner via resumeHook()'s runId
instead of threading conflictingRunId through the workflow result.
- Update claim-pattern prose across start(), getHookByToken(), world
storage, scheduling, workflow composition, and cookbook idempotency
pages (v4 + v5).
- Add @skip-typecheck marker to the cross-block route sample, fixing a
pre-existing docs typecheck failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: move resume-or-start guidance into a dedicated resumeHook example
The early callout was too vague and out of place at the top of the API
reference. Replace it with a 'Resume or Start' example section that
explains the flow, shows the resume-first/start-then-retry route, and
links to the run idempotency pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: detect the concurrent-start race via runId comparison instead of awaiting returnValue
The 'Resume or Start' example returned the just-started run's runId with
reused: false even when a concurrent request's run won the token race —
the payload had reached the actual owner, so the response pointed callers
at a run that exits as a duplicate. The foundations route handled the
race correctly but by awaiting run.returnValue, blocking the HTTP
response on full workflow completion.
resumeHook() always resolves against the actual active owner, so
comparing the resumed hook's runId with the started run's runId detects
the race in both examples — race-correct and non-blocking.
* feat: replace hook.hasConflict with hook.getConflict (Promise<Run | null>)
hasConflict's boolean didn't expose WHICH run owns the token, so the
duplicate run couldn't act on the conflict. getConflict resolves with
null once registration commits, or with a Run handle for the conflicting
run — letting the workflow return/log the owner's runId, inspect its
status, await its result, or cancel it and continue, all in code.
The workflow-mode create-hook module exposes the bundle's compiled Run
class (durable step-proxy methods) on a well-known symbol so the host-
side hook consumer can construct the conflicting run inside the VM.
Contexts without the class (plain unit tests) fall back to a { runId }
object, which is also the documented v4 shape (no native Run
serialization in v4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: adopt hook.getConflict and add conflict-handling strategy guide
Run idempotency docs now use getConflict (resolves with the conflicting
Run in v5, { runId } in v4) and document code-driven conflict strategies
in place of static ID-reuse policies: reject the duplicate, adopt the
owner's result, inspect before deciding, signal the owner via
resumeHook, and supersede via cancel-and-reclaim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: never resolve getConflict with a non-Run fallback shape
getConflict's contract is Promise<Run | null>. In the degenerate cases
where a real Run cannot be constructed — a hook_conflict event persisted
by an old world without conflictingRunId, or a context that never loaded
the workflow-mode create-hook module — reject with HookConflictError
instead of resolving with a { runId }-shaped impostor.
Test harnesses now register the Run class on the (VM) globalThis like
real bundles do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: make getConflict a method — hook.getConflict()
A property getter that triggers registration/suspension reads as passive
state; a method makes the side effect explicit. Update implementation,
types, tests, e2e workflows, docs, and changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: getConflict is a method — hook.getConflict()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: typecheck every sample — drop skip-typecheck escape hatches
Route examples typecheck as-is since the runId-comparison rewrite;
strategy fragments are now complete self-contained workflows; the
publishing-libraries cross-block dependency uses the declare @setup
convention. 934 samples typechecked, none skipped by this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* review: guard Run class registration, fix anchors, clarify changeset
- Only register WORKFLOW_RUN_CLASS when the workflow runtime is present
(WORKFLOW_CREATE_HOOK installed on globalThis), so host imports of the
workflow-mode module neither mutate the host global nor expose the
non-step-proxy host Run.
- Drop #run-idempotency link fragments — that section lands in the
stacked docs PR (#2011), which restores the anchored links.
- Note in docs that getConflict() rejects with HookConflictError for
legacy hook_conflict events lacking the owner's run ID.
- Changeset now calls out the hasConflict -> getConflict() replacement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: restore run-idempotency anchors now that the section exists here
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: describe fixed conflict policies generically, without naming other systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions
github-actionsBot requested a review from a team as a code ownerJune 14, 2026 08:06
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b20212

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious

Copy link
Copy Markdown
Member

Closing because docs are maintained on main

Sign up for freeto 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.

1 participant

@VaguelySerious