Skip to content

[core] Settle a hook's awaiter in-process instead of re-invoking, on creation and on conflict - #3938

Merged
pranaygp merged 2 commits into
mainfrom
hook-conflict-in-process
Sep 3, 2026
Merged

pranaygp merged 2 commits into
mainfrom
hook-conflict-in-process

Conversation

@pranaygp

@pranaygp pranaygp commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

The problem

A suspension whose only pending continuation is a hook's own awaiter is resolved by the event that hook's create commits, and by nothing else. The runtime answered both of those events with reinvoke(0):

if (suspensionResult.hasHookConflict) {
  return await reinvoke(0);          // queue hop + cold replay
}
...
if (inlineExecutions.length === 0) {
  if (suspensionResult.hasAwaitedHookCreation) {
    return await reinvoke(0);        // queue hop + cold replay
  }
  return;
}

So the invocation wrote the event, threw away the VM that was one await from consuming it, handed the run to the queue, and paid a full replay to read back what it had just written. The parked VM was discarded because the boundary was unretainable: getRetentionDecision refuses a suspension with no step or attribute write as no_replay_driver, on the grounds that nothing in the current delivery can advance it — which is true of a hook-only boundary in general, and false of this one.

A create takes one of two outcomes and both settle that awaiter: hook_created for a clean registration, and hook_conflict when another run already holds the token — rejecting a payload await, or resolving hook.getConflict() with the conflicting run. Same write, same event slot, same continuation.

The change

Carry the log forward on the write. handleSuspension now sets CreateEventParams.sinceCursor on the hook create (the same contract the step-terminal write already uses), folds a complete delta into the caller's loaded log, and reports eventLogCarriedForward — true only when the delta accounted for every event the suspension committed. Asked for on the single-hook suspension only: two creates issued from one snapshot diff against the same cursor and only the first delta can be folded in, so the log would end up short of the other's event with nothing to say so. The delta is indifferent to which event was committed — it is the slice of the log after the caller's cursor either way.

Server side: vercel/workflow-server#913.

Continue on the retained VM, on either outcome. Both branches resume the parked session in the same invocation instead of re-invoking, through one continueOverHookWrite helper. When the delta carried the log forward the next iteration reads nothing at all; otherwise it does one incremental read from the cursor — still cheaper than the delivery round-trip and full replay it replaces. The conflict branch keeps its position ahead of the attribute detour and all step dispatch: a Promise.race between the hook and a step must still let the durable conflict win without executing the losing step, so what changes is only how the workflow gets to observe it.

Retain across exactly this boundary. getRetentionDecision grows a hookContinuation arm. main already retains across open hooks and waits when a step or attribute write drives the next iteration, and refuses a hook- or wait-only suspension as no_replay_driver. Here the runtime itself is the driver: the continuation resumes the session in-process, so the boundary is retained on its own terms. Steps in the same suspension ride along queued (an awaiter empties lazyInlineSteps, and the conflict branch returns before any inline execution). An open wait in the log does not block retention but does force the incremental read before resuming, for the reason the inline-delta gate already gives: a wait_completed is a resolution the replay is waiting on rather than an event it can observe an iteration late.

Hook signaler guard. The hook consumer's suspension signal must carry the generation guard for this boundary to be retainable at all: without it, a signal armed for the boundary the resume moved past would raise a suspension the workflow never reached. main now provides that through the shared scheduleWorkflowSuspension helper (#3604), so this PR no longer adds its own; it keeps the two hook.test.ts cases that pin the behavior (stale signal dropped, live signal still lands).

Worlds. world-sim and world-local both return a hook_conflict early, ahead of their shared sinceCursor block, so they answered no delta on exactly the write that asked for one. Both now compute it there. CreateEventParams.sinceCursor documents that a World answering the delta on hook_created must answer it on hook_conflict.

Docs. The WORKFLOW_RETAINED_VM entry in runtime tuning said hook-only suspensions always park the invocation. It now names this one exception.

What is deliberately unchanged

  • Dependent inline steps are still not run eagerly. An awaiter still empties lazyInlineSteps, so every step the suspension schedules is queued and the continuation races them rather than waiting behind a step body. The continuation happens before any inline execution, so the ordering the empty batch protects is preserved. (The conflict branch gets this for free — it returns before step dispatch entirely.)
  • A conflict still aborts this pass's scheduling. No wait timeout is reported and no step is dispatched; the pass that continues over the conflict schedules them, exactly as the re-invocation's replay used to.
  • Escape hatches. WORKFLOW_RETAINED_VM=0 restores the re-invocation exactly. So does a World that returns no delta and a read that still does not show the write: continuations are tracked by hook id, so a repeat for the same hook falls back to reinvoke(0) rather than spinning, while a workflow creating one awaited hook after another keeps continuing in-process for each.

Also lifts appendEventLog into runtime/helpers.ts — the suspension handler and the replay loop now share the one log-append primitive instead of keeping two copies that have to agree.

Testing

Rebased onto main after #3604 / #3609 / #3892 landed. packages/core: 2351 passed (+ 3 expected fail, 1 skipped). world-sim and world-local suites pass. tsc clean on every changed package.

New coverage:

  • runtime/suspension-handler.test.ts — the created hook event is folded into the caller's log and the cursor moves with it; the same for a committed hook_conflict, which is reported by hook id with hasAwaitedHookCreation: false; eventLogCarriedForward is false when a step also wrote, when a wait also wrote alongside a conflict, when two hooks are created, when the delta is truncated, when the World returns none, and when the log has no cursor (turbo); an awaiter-less conflict defers its step and so still carries the log forward.
  • retained-vm-loop.test.ts — a getConflict workflow completes inside a single delivery with one events.list and zero queue sends. The same for a conflicting create, both with a getConflict() branch and with a plain payload await (the shape that reports only hasHookConflict). Both still complete, with an extra read, when the World withholds the delta; both fall back to the re-invocation under WORKFLOW_RETAINED_VM=0; and a repeat pass that still cannot see the conflict re-invokes after exactly one retry instead of spinning. The harness now models sinceCursor, token conflicts, and a lagging read the way a World does.
  • workflow/hook.test.ts — a signal armed for a boundary the run has moved past is dropped, with a control proving one that is still wanted lands. These pin the guard main already provides (the boundary here depends on it); they are driven by explicit macrotask turns rather than a wall-clock sleep.
  • world-sim/store.test.ts, world-local/storage.test.ts — a conflicting create answers the sinceCursor delta with the hook_conflict on it, byte-identical to a list from the same cursor, and omits it when the caller did not ask.

Every new test except the two hook.test.ts pins was verified to fail without the corresponding production change.

Docs Preview

Page Preview
Runtime tuning WORKFLOW_RETAINED_VM

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 2, 2026 18:48
@pranaygp
pranaygp requested a review from a team as a code owner September 2, 2026 18:48
@vercel

vercel Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
example-nextjs-workflow-turbopack Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
example-nextjs-workflow-webpack Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
example-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-astro-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-express-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-fastify-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-hono-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-nestjs-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-nitro-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-nuxt-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-python-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-sveltekit-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-tanstack-start-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workbench-vite-workflow Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workflow-docs Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workflow-swc-playground Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workflow-tarballs Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC
workflow-web Ready Ready Preview, v0 Sep 3, 2026 7:12pm UTC

@changeset-bot

changeset-bot Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f17930b

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

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The newly added hook tests rely on a fixed real-time sleep (setTimeout(..., 20)), which can introduce avoidable flakiness and should be made deterministic.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR optimizes the hook.getConflict() awaiter path in @workflow/core by continuing execution in-process (via retained VM) after a hook_created write, instead of forcing a queue re-invocation and cold replay to read back the just-written event. It extends the existing sinceCursor “inline delta” contract so hook creation can carry the event log forward when it is the sole continuation for the suspension.

Changes:

  • Request and (when safe) fold an inline event-log delta (CreateEventParams.sinceCursor) from hook_created writes so the caller can continue without an events.list round-trip.
  • Relax VM retention for the specific “awaited hook creation” boundary and resume the retained session in-process; keep a fallback to reinvoke(0) for repeats / kill switch / missing delta.
  • Add generation-guarded hook suspension signaling and new tests covering the in-process continuation and delta-folding behavior.
File summaries
File Description
packages/world/src/events.ts Documents the expanded sinceCursor delta use for hook_created and updates EventResult producer docs.
packages/world-sim/src/store.ts Mirrors backend behavior by returning sinceCursor deltas for hook_created (in addition to step-terminal events).
packages/core/src/workflow/hook.ts Adds a suspension-generation guard to hook idle signaling to avoid stale suspensions after retained-session resume.
packages/core/src/workflow/hook.test.ts Adds tests verifying stale hook suspension signals are dropped and live ones still fire.
packages/core/src/runtime/suspension-handler.ts Requests/absorbs sinceCursor deltas for single-hook awaited creations and reports eventLogCarriedForward + awaited hook IDs.
packages/core/src/runtime/suspension-handler.test.ts Adds coverage for delta folding, cursor movement, and “carry-forward” gating conditions.
packages/core/src/runtime/helpers.ts Extracts appendEventLog helper shared by runtime loop and suspension handler.
packages/core/src/runtime.ts Extends retention predicate for awaited hook creation and resumes retained VM in-process for hook.getConflict() awaiters.
packages/core/src/retained-vm-loop.test.ts Adds an integration-style harness test asserting the awaiter resolves within one delivery (and fallback cases).
packages/core/src/private.ts Updates docs to reflect hook suspension signals now use the generation guard.
.changeset/hook-conflict-in-process.md Changeset describing the optimization and fallback behavior.
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/core/src/workflow/hook.test.ts Outdated
Comment on lines +62 to +65
/** Let every queued timer and microtask settle. */
function settleTimers(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 20));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — settleTimers now awaits a handful of explicit setTimeout(0) turns (the unit scheduleWhenIdle polls in) instead of a fixed 20ms sleep.

Comment thread .changeset/hook-conflict-in-process.md Outdated
'@workflow/world': patch
---

Resolve a `hook.getConflict()` awaiter in the invocation that created the hook instead of re-invoking through the queue. The `hook_created` write now asks for the event-log delta since the replay's cursor (`CreateEventParams.sinceCursor`), and the runtime resumes the retained VM over the returned event — removing a delivery round-trip and a cold replay per awaited hook. Worlds that return no delta fall back to an incremental read, and `WORKFLOW_RETAINED_VM=0` restores the re-invocation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Resolve a `hook.getConflict()` awaiter in the invocation that created the hook instead of re-invoking through the queue. The `hook_created` write now asks for the event-log delta since the replay's cursor (`CreateEventParams.sinceCursor`), and the runtime resumes the retained VM over the returned event — removing a delivery round-trip and a cold replay per awaited hook. Worlds that return no delta fall back to an incremental read, and `WORKFLOW_RETAINED_VM=0` restores the re-invocation.
Resolve `hook.getConflict()` in the same invocation after creating the hook, avoiding a queue hop and replay. Fall back to an incremental read or the existing re-invocation path when needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took the terser shape, and split it per package so each entry says only what changed in it: @workflow/core gets the in-process settle (now covering both hook_created and hook_conflict, since the conflict path landed in the same PR), and the three World packages get a one-liner about answering the sinceCursor delta on hook_conflict.

@karthikscale3 karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of scope for this PR, but wondering separately if there is any harm in optimizing the hook_conflict scenario also to return the event log and continue in process instead of re enqueuing by default?

@karthikscale3 karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the retained-VM continuation path and its fallbacks. The hook_created delta is only consumed when complete; missing or partial deltas fall back safely, and conflicts retain the existing re-invocation behavior. Approved.

@pranaygp pranaygp changed the title [core] Resolve a hook.getConflict() awaiter in-process instead of re-invoking [core] Settle a hook's awaiter in-process instead of re-invoking, on creation and on conflict Sep 2, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 world-sim scenario book — 1 fail of 41 total

fence=per-spec

scenario outcome events virt replay violations
✅ smoke-no-steps completed 3 0ms ok 0
✅ smoke-one-step completed 6 0ms ok 0
✅ hook-at-step-started completed 12 0ms ok 0
✅ hook-at-step-completed completed 12 0ms ok 0
✅ hook-at-hook-created completed 12 0ms ok 0
✅ deadline-hook-wins completed 7 1.0h ok 0
✅ deadline-expires completed 7 1.0h ok 0
✅ long-sleep completed 11 30.0d ok 0
✅ hook-never-arrives stalled 3 0ms skipped 0
✅ step-retries-twice completed 10 2.0s ok 0
✅ parallel-steps completed 9 0ms ok 0
✅ hook-on-execution-state completed 12 0ms ok 0
✅ peek-hook-before-branch completed 12 0ms ok 0
✅ peek-hook-after-branch completed 12 0ms ok 0
✅ peek-hook-at-registration completed 12 0ms ok 0
✅ race-hook-before-probe completed 12 0ms ok 0
✅ race-hook-after-probe completed 12 0ms ok 0
✅ race-duplicate-delivery completed 13 0ms ok 0
✅ attr-hook-before-step completed 11 0ms ok 0
✅ attr-hook-after-step completed 11 0ms ok 0
✅ attr-from-step-body completed 13 0ms ok 0
✅ fork-hook-after-timeout completed 14 1.0m ok 0
✅ fork-hook-before-timeout completed 14 1.0m ok 0
✅ count-hook-after-timeout completed 17 1.0m ok 0
✅ count-hook-before-timeout completed 20 1.0m ok 0
✅ stale-read-step-count-fork completed 20 1.0m ok 0
✅ stale-read-equal-step-counts completed 14 1.0m ok 0
✅ step-vs-step-fork completed 12 0ms ok 0
✅ step-vs-step-fork-fenced completed 12 0ms ok 0
✅ fence-catches-benign-direction completed 12 5ms ok 0
✅ in-flight-before-decision completed 17 1.0m ok 0
❌ in-flight-before-decision-counted completed 17 1.0m ok 0
✅ in-flight-after-decision completed 19 2.0m ok 0
✅ stale-read-step-count-fork-fenced completed 20 1.0m ok 0
✅ fork-hook-wins completed 13 1.0m ok 0
✅ fork-timeout-wins completed 13 1.0m ok 0
✅ unclaimed-payload-under-fork completed 17 1.0m ok 0
✅ claimed-payload-under-fork completed 17 1.0m ok 0
✅ writers-independent-step-bodies completed 12 0ms ok 0
✅ writers-scripted-tempo completed 12 0ms ok 0
✅ cancel-mid-step cancelled 7 0ms skipped 0

Full trace: world-sim.txt

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor
Framework Flow route Step reg. Framework output
hono 200.9 KiB (±0) 40.6 KiB (±0) 1.77 MiB (+1.9 KiB)
nextjs-turbopack 206.3 KiB (±0) 439 B (±0) 766.2 KiB (+442 B)
About these numbers

Sizes are gzip; parentheses show the change against main.
Flow route and Step reg. gate this job, on raw bytes rather than the gzip shown, at max(2%, 50.0 KiB). Framework output is informational.

f17930b · run

@pranaygp
pranaygp merged commit 7cc5c88 into main Sep 3, 2026
318 of 325 checks passed
@pranaygp
pranaygp deleted the hook-conflict-in-process branch September 3, 2026 20:35
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

No backport to stable for 7cc5c88 (AI decision).

This is a performance optimization, not a stability fix: it settles a hook's awaiter on the retained VM in the writing invocation instead of paying a queue hop plus cold replay, and it adds new surface (CreateEventParams.sinceCursor on hook creates, eventLogCarriedForward, awaitedHookCorrelationIds/hookConflictCorrelationIds, a hookContinuation retention arm, and world-side delta answers on hook_conflict). The existing re-invocation path is correct on stable, merely slower, so it does not meet the bar for a maintenance line. It also builds on main-only infrastructure — packages/core/src/retained-vm-loop.test.ts and packages/world-sim do not exist on origin/stable (verified with git ls-tree) — and it depends on the scheduleWorkflowSuspension generation guard from #3604.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

7cc5c88a8bb2fad48353dd006c6ca1f28190ab46

pranaygp added a commit that referenced this pull request Sep 3, 2026
…elta changeset (#3963)

Changesets refuses a changeset that mixes packages in the `ignore` list
with published ones, so `changeset version` has failed on every push to
main since #3938 and nothing has been published.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request Sep 3, 2026
pranaygp added a commit that referenced this pull request Sep 3, 2026
…Release job

`changeset version` assembles a release plan from every pending changeset
before it bumps anything, and throws on a changeset it cannot place there:
one naming a package outside the workspace, or one mixing a package from
the `ignore` list with published ones. #3938 shipped the latter and every
push to main since has failed to publish (#3963). Nothing at PR time ran
that step.

scripts/check-changesets.mjs runs the same assembly on the same inputs,
resolving the libraries from @changesets/cli's own install so the check
uses exactly the versions the Release job does, and stops before the
network-bound changelog generation. lint.yml runs it on every PR.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pranaygp added a commit that referenced this pull request Sep 4, 2026
…Release job (#3964)

`changeset version` assembles a release plan from every pending changeset
before it bumps anything, and throws on a changeset it cannot place there:
one naming a package outside the workspace, or one mixing a package from
the `ignore` list with published ones. #3938 shipped the latter and every
push to main since has failed to publish (#3963). Nothing at PR time ran
that step.

scripts/check-changesets.mjs runs the same assembly on the same inputs,
resolving the libraries from @changesets/cli's own install so the check
uses exactly the versions the Release job does, and stops before the
network-bound changelog generation. lint.yml runs it on every PR.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
pranaygp added a commit that referenced this pull request Sep 9, 2026
…c-workflow-source

* origin/main: (22 commits)
  feat(streams): add writer session seam (#3832)
  docs: document WORKFLOW_NODE_HTTP in the v4 World docs (#4050)
  [world-vercel] Honor WORKFLOW_NODE_HTTP on the queue transport (#4044)
  feat(streams): add WebSocket capability gate (#3764)
  fix(world-local): retry JSON reads on Windows (#4051)
  [core] Add the wake-loop scenario to the event log race repro (#4017)
  [ci] Cap concurrent Vercel E2E action repo-wide (10 by default) (#4039)
  Add `Run#getWritable()` for appending to another run's stream (#3972)
  feat(streams): add WebSocket v1 client protocol contract (#3763)
  [swc-playground] Update to Next.js v16.3.4 (#4018)
  [core] Add a retention option to start() (#3787)
  fix(swc-plugin): register class expressions via an IIFE instead of by name (#3971)
  [docs] Fix prose typos across v4/v5 docs and the SWC plugin README (#3948)
  Validate pending changesets in CI so a bad one fails the PR, not the Release job (#3964)
  Version Packages (beta) (#3919)
  Classify Workflow stream failures (#3850)
  Drop the ignored @workflow/world-sim package from the hook_conflict delta changeset (#3963)
  Add attribute inspection to the CLI (#3950)
  [core] Settle a hook's awaiter in-process instead of re-invoking, on creation and on conflict (#3938)
  Use the storage APIs for run detail views (#3944)
  ...

This branch was successfully deployed

18 active deployments
Preview – workflow-swc-playground — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workflow-docs — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – example-nextjs-workflow-webpack — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-nuxt-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-sveltekit-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – example-nextjs-workflow-turbopack — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workflow-tarballs — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-express-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-nestjs-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-tanstack-start-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-vite-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-hono-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – example-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-fastify-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-astro-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-nitro-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workflow-web — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Preview – workbench-python-workflow — f17930b3 Deployed Sep 3, 2026 by vercel[bot]
Sign up for free to 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.

3 participants