Skip to content

feat(runner): queue-on-running so back-to-back handoffs don't race#24

Merged
volodchenkov merged 2 commits into
mainfrom
feat/queue-on-running
Jun 18, 2026
Merged

feat(runner): queue-on-running so back-to-back handoffs don't race#24
volodchenkov merged 2 commits into
mainfrom
feat/queue-on-running

Conversation

@volodchenkov

@volodchenkov volodchenkov commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • New Runner.request_spawn(...): spawn-or-queue. If the (slug, nick, issue) triple is already in flight, the request is stored in _pending (last-write-wins) and fired automatically when the active supervisor exits.
  • Webhook switched to request_spawn — old SessionAlreadyRunningError → skipped: already running path retired and replaced with status: queued → skipped: queued.
  • wait_idle re-snapshots _tasks until drained so flush tasks scheduled mid-shutdown don't leak open log fps.

Why

An agent that calls request_handoff(target_role='<self>') to schedule its own next step hits a race: Plane delivers the comment-webhook to conductor ~milliseconds before the previous subprocess has cleared _active. The old direct-spawn path silently dropped the re-entry as a duplicate and the chain died.

This unblocks planned step-execution discipline prompt patches on claude-sdlc-agents (coders/testers/SA: one step per invocation + self-handoff → no 14-step token burn or hangs). A prompt rule that depends on a racy spawn would be a lie, so this lands first.

Semantics

  • Capacity cap: still a hard limit. Queued spawns don't count toward it; request_spawn re-raises CapacityFullError rather than queueing.
  • Coalescing: multiple handoffs targeting the same triple while one is active overwrite the pending entry — the next agent reads actual state from Plane, not from queued trigger metadata, so the latest one is enough.
  • Direct spawn(): unchanged; still raises on conflict. Webhook moves to request_spawn. No external callers of spawn today.
  • Errors during flush: logged and swallowed. The original webhook returned 200 to Plane long ago — there's no caller to surface to.

Test plan

  • request_spawn fires immediately on a free triple
  • Second request for same triple while active → status: queued, pending_count == 1
  • Third request → coalesces, still pending_count == 1
  • Independent triples run in parallel
  • Queued spawn flushes after active session for the same key exits
  • Capacity full still raises
  • Webhook stub updated, new test covers skipped: queued
  • Full suite: 224 passed, 7 skipped (e2e)
  • ruff + ruff-format + mypy: clean

Out of scope

Prompt patches for step-execution discipline ship in a follow-up PR on claude-sdlc-agents after this lands.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Duplicate trigger requests are now queued for the same workspace/nickname/issue combination and automatically processed when the current run finishes (last request wins for the pending item).
    • Webhook responses now distinguish between immediate spawns and queued requests via a returned status.
  • Bug Fixes
    • Improved webhook handling for already-running scenarios by relying on queued status rather than error-based detection.
  • Tests
    • Added coverage for spawn vs. queue behavior, pending flush on exit, capacity-full raising, and parallel independent triples.
  • Chores
    • Added a dev-only HTTP testing dependency.

Background
----------
An agent that calls `request_handoff(target_role='<self>')` to schedule its
own next step hits a race: Plane delivers the comment-webhook to conductor
~milliseconds before the previous subprocess has cleared `_active`. The
old webhook path called `runner.spawn()` directly, which raised
`SessionAlreadyRunningError` for the in-flight triple and recorded
`skipped: already running` — the chain died there.

This blocked the "step-execution discipline" we want to write into the
coder / tester / SA prompts (one step per invocation + self-handoff to
avoid 14-step token burn and hangs). A prompt rule that depends on a
racy spawn would be a lie.

Change
------
- New `Runner.request_spawn(...)`: spawn-or-queue. If the
  `(slug, nick, issue)` triple is already in flight, the request is stored
  in `_pending` keyed by the same triple (last-write-wins — multiple
  handoffs targeting the same triple coalesce, since the next agent reads
  state from Plane, not from queued trigger metadata).
- `_supervise` flushes the queued spawn from its `finally` block, on a
  fresh task so it doesn't pin the dying supervisor's log fp / transport.
- Direct `spawn()` keeps raising — webhook callers move to `request_spawn`,
  long-running callers (none today) can still opt in to the strict path.
- Capacity cap stays a hard limit: queued spawns don't count toward the
  cap, and `request_spawn` re-raises `CapacityFullError` rather than
  queueing — same semantics as before for that case.
- `wait_idle` now re-snapshots `_tasks` until the set drains, because
  flush tasks are added AFTER their parent `_supervise` is already done;
  a single `gather(*_tasks)` would miss them and leak the flushed log fp.
- `_flush_pending` swallows errors with structured logs (the original
  webhook returned 200 to Plane long ago — there's no caller to surface
  to). Lost-race → `SessionAlreadyRunning` → info-log only.

Tests
-----
- `request_spawn` fires immediately on a free triple.
- Second request for the same triple while active → `status: queued`,
  `pending_count == 1`.
- Third request for the same triple → coalesces, still `pending_count == 1`.
- Independent triples run in parallel (no false queueing across keys).
- Queued spawn flushes after the active session for the same key exits.
- Capacity full still raises (request_spawn does not bypass the cap).
- Webhook stub updated to mock `request_spawn`. New webhook test covers
  the queued path returning `skipped: queued`. Old "skipped: already
  running" path retired (no caller for it).

Out of scope
------------
Prompt patches for step-execution discipline (coders / testers / SA)
ship in a follow-up PR on claude-sdlc-agents — they depend on this
landing first.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

runner.py gains a _PendingSpawn dataclass, a _pending map, and request_spawn method that queues duplicate (workspace_slug, nickname, issue_uuid) triples instead of raising. _supervise schedules _flush_pending on process exit; wait_idle is reworked to drain newly-added flush tasks. webhook.py switches from runner.spawn to runner.request_spawn and surfaces "queued" as a skip reason. Tests cover all new paths and dev dependency is added.

Changes

Spawn-or-queue concurrency feature

Layer / File(s) Summary
_PendingSpawn dataclass and request_spawn logic
src/plane_conductor/runner.py
Updates module and in-memory state docs to describe queue-on-running semantics. Adds _PendingSpawn dataclass, extends Runner with _pending map and pending_count property, and implements request_spawn to spawn immediately or enqueue with last-write-wins when the triple is already active. Updates spawn() docstring to direct webhook callers to request_spawn.
_supervise exit cleanup and _flush_pending
src/plane_conductor/runner.py
Modifies _supervise to pop active state on process exit and schedule _flush_pending as a tracked task when a pending entry exists. _flush_pending performs the deferred spawn() and swallows/logs capacity-full, already-running, and agent-spawn errors.
wait_idle snapshot-and-drain loop
src/plane_conductor/runner.py
Reworks wait_idle to repeatedly snapshot-and-await _tasks until drained (accounting for flush tasks added after supervisors complete) and adds a final drain pass after the grace period with explicit timeout handling.
Webhook switches to request_spawn
src/plane_conductor/webhook.py
Removes SessionAlreadyRunningError import, replaces runner.spawn(...) with runner.request_spawn(...), routes status == "queued" to skipped with reason "queued", and removes the old exception-based duplicate-trigger branch.
Runner and webhook tests for request_spawn
tests/test_runner.py, tests/test_webhook.py
Adds five runner tests covering immediate spawn, coalescing queue, flush-on-exit drain, capacity-full raise, and parallel-triple parallelism. Updates webhook stubs to request_spawn and replaces the SessionAlreadyRunningError dedup test with a queued-status test.
Dev dependency for httpx2
pyproject.toml
Adds httpx2>=0.1 to dev dependencies as a workaround for Starlette TestClient backend selection during pytest collection.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(173, 216, 230, 0.5)
        Note over Webhook,Runner: Duplicate spawn request while active
    end
    Webhook->>Runner: request_spawn(workspace, nickname, issue_uuid)
    Runner-->>Webhook: {status: "spawned", log_path: ...}

    Webhook->>Runner: request_spawn(workspace, nickname, issue_uuid)
    Runner-->>Webhook: {status: "queued"}

    Note over Runner: Active session process exits
    Runner->>Runner: _supervise removes active state
    Runner->>Runner: _flush_pending scheduled as new task
    Runner->>Runner: spawn(pending args) — deferred execution
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • volodchenkov/plane-conductor#5: Modifies the same webhook mention→spawn flow in webhook.py and runner.py, including the workspace-scoped dedup routing that this PR replaces with the request_spawn queue mechanism.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly captures the main change: introducing a queue-on-running mechanism to handle back-to-back handoffs without race conditions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/queue-on-running

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plane_conductor/webhook.py (1)

326-349: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore SessionAlreadyRunningError mapping to skipped in webhook spawn handling.

This path no longer catches SessionAlreadyRunningError, so that case can return 500 instead of a skipped response.

💡 Suggested patch
             try:
                 spawn_result = await runner.request_spawn(
                     workspace=workspace,
                     plane=plane,
                     nickname=nickname,
                     issue_uuid=issue_uuid,
                     triggered_by_email=email,
                 )
                 if spawn_result.get("status") == "queued":
                     log.info(
                         "spawn_deferred_until_active_clears",
                         workspace=slug,
                         nickname=nickname,
                         issue=str(issue_uuid),
                     )
                     skipped.append({"nickname": nickname, "reason": "queued"})
                 else:
                     spawned.append(nickname)
+            except SessionAlreadyRunningError:
+                log.info(
+                    "already_running",
+                    workspace=slug,
+                    nickname=nickname,
+                    issue=str(issue_uuid),
+                )
+                skipped.append({"nickname": nickname, "reason": "queued"})
             except CapacityFullError:
                 log.warning("capacity_full", workspace=slug, nickname=nickname)
                 skipped.append({"nickname": nickname, "reason": "capacity full"})
             except AgentSpawnError as exc:
                 log.error("spawn_failed", workspace=slug, nickname=nickname, error=str(exc))
                 skipped.append({"nickname": nickname, "reason": "spawn failed"})

Also re-add SessionAlreadyRunningError to this module’s exceptions import list.

As per coding guidelines, src/plane_conductor/webhook.py: “webhook must surface SessionAlreadyRunningError as skipped, not 500.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plane_conductor/webhook.py` around lines 326 - 349, The webhook spawn
handling is missing an exception handler for SessionAlreadyRunningError which
should be treated as a skipped item rather than an error. Add an except block
for SessionAlreadyRunningError after the AgentSpawnError handler in the
try-except block surrounding the runner.request_spawn() call, logging a warning
with appropriate context (workspace slug, nickname) and appending to the skipped
list with reason "session already running". Additionally, ensure
SessionAlreadyRunningError is imported at the top of the module in the
exceptions import list.

Source: Coding guidelines

🧹 Nitpick comments (1)
tests/test_webhook.py (1)

496-511: ⚡ Quick win

Add a regression test for SessionAlreadyRunningErrorskipped mapping.

This new queued-status test is good, but it no longer guards the explicit exception contract. Please add a companion case where request_spawn() raises SessionAlreadyRunningError and assert HTTP 200 with a skipped entry.

💡 Suggested test shape
+def test_webhook_skips_when_runner_raises_already_running(
+    settings: Settings, workspace_config: WorkspaceConfig
+) -> None:
+    from plane_conductor.exceptions import SessionAlreadyRunningError
+
+    class DuplicateRunner:
+        async def request_spawn(self, **kwargs: Any) -> dict[str, Any]:
+            raise SessionAlreadyRunningError("already running")
+
+    plane = StubPlane({SARK: {"email": "sark@example.io"}})
+    client = TestClient(_app(settings, workspace_config, plane, DuplicateRunner()))
+    resp = _send(client, settings, workspace_config, _comment_body(SARK))
+    assert resp.status_code == 200
+    assert resp.json()["skipped"] == [{"nickname": "sark", "reason": "queued"}]

As per coding guidelines, src/plane_conductor/webhook.py: “webhook must surface SessionAlreadyRunningError as skipped, not 500.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_webhook.py` around lines 496 - 511, Add a new regression test
function alongside test_webhook_queues_when_runner_reports_already_running that
explicitly tests the SessionAlreadyRunningError exception path. Create a
QueueingRunner class that raises SessionAlreadyRunningError from its
request_spawn method (instead of returning a status dict), then send the webhook
request and assert HTTP 200 status with a skipped entry in the response JSON
containing the appropriate reason for the exception. This ensures the webhook
properly handles the SessionAlreadyRunningError exception and surfaces it as a
skipped entry rather than returning a 500 error.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plane_conductor/runner.py`:
- Around line 416-457: The _flush_pending method's docstring promises errors are
never raised, but the current exception handling only catches
SessionAlreadyRunningError, CapacityFullError, and AgentSpawnError. Other
exceptions like PermissionError or OSError from create_subprocess_exec could
escape. Add a catch-all exception handler after the existing except clauses that
catches Exception, logs the error with the same structured logging format
(including workspace.workspace_slug, nickname, issue, and error details), and
ensures the method never re-raises, maintaining the guarantee that all errors
are logged and swallowed.
- Around line 606-610: The final drain loop (where _tasks is awaited) can hang
indefinitely because _flush_pending calls spawn() which creates new supervisor
tasks that weren't killed during the grace period expiration. To fix this,
either add a shutdown flag that prevents spawn() from creating new processes
during the drain phase, or add a timeout parameter to the asyncio.gather call in
the final drain loop to ensure it completes within a reasonable timeframe
regardless of newly spawned tasks. The key is to prevent new tasks from being
indefinitely awaited after the known processes have been killed.

---

Outside diff comments:
In `@src/plane_conductor/webhook.py`:
- Around line 326-349: The webhook spawn handling is missing an exception
handler for SessionAlreadyRunningError which should be treated as a skipped item
rather than an error. Add an except block for SessionAlreadyRunningError after
the AgentSpawnError handler in the try-except block surrounding the
runner.request_spawn() call, logging a warning with appropriate context
(workspace slug, nickname) and appending to the skipped list with reason
"session already running". Additionally, ensure SessionAlreadyRunningError is
imported at the top of the module in the exceptions import list.

---

Nitpick comments:
In `@tests/test_webhook.py`:
- Around line 496-511: Add a new regression test function alongside
test_webhook_queues_when_runner_reports_already_running that explicitly tests
the SessionAlreadyRunningError exception path. Create a QueueingRunner class
that raises SessionAlreadyRunningError from its request_spawn method (instead of
returning a status dict), then send the webhook request and assert HTTP 200
status with a skipped entry in the response JSON containing the appropriate
reason for the exception. This ensures the webhook properly handles the
SessionAlreadyRunningError exception and surfaces it as a skipped entry rather
than returning a 500 error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: da834cce-ceaf-4448-961d-7a0dc317b2e3

📥 Commits

Reviewing files that changed from the base of the PR and between a7437e8 and dba7ac9.

📒 Files selected for processing (4)
  • src/plane_conductor/runner.py
  • src/plane_conductor/webhook.py
  • tests/test_runner.py
  • tests/test_webhook.py

Comment thread src/plane_conductor/runner.py
Comment thread src/plane_conductor/runner.py Outdated
…ndling

CI on Python 3.11 broke on collection because starlette 1.x's TestClient
imports `httpx2` and emits StarletteDeprecationWarning when falling back
to httpx — strict-mode pytest (`filterwarnings = ["error"]`) turns the
warning into a collection error.

Locally the .venv still had starlette 1.0.0 cached; reproduced by upgrading
to 1.3.1 and confirmed the same failure. Adding `httpx2` to dev-deps lets
TestClient pick its preferred backend and the warning never fires. Once
fastapi pulls in httpx2 by default this entry can drop out — leaving a
comment.

Also addresses two CodeRabbit findings from PR #24 review:

- `_flush_pending`: add a catch-all `Exception` arm. The flush task runs
  detached from any webhook caller, so an uncaught OSError / PermissionError
  / unexpected subprocess failure would only surface in asyncio's unraisable
  hook. Log it structurally instead and keep the contract that the flush
  never escapes.
- `wait_idle` post-kill drain: bound the final loop with a 5s deadline. A
  flushed spawn that itself blocks (broken plane API, hung child) could
  otherwise pin shutdown indefinitely. Timeout → warning + break.

Skipped: CodeRabbit asked to re-add a `SessionAlreadyRunningError` catch
in the webhook and a matching test. `request_spawn` contractually does NOT
raise that error (it queues), so the catch would be dead code and the test
would assert a path that can't be reached. Left a thread reply explaining.

Tests: 224 passed, 7 skipped (e2e). ruff + ruff-format + mypy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyproject.toml`:
- Around line 48-51: The dependency pinned in the test requirements is
incorrect. Replace the package name from httpx2 to httpx in the test
dependencies section. The comment references Starlette 1.0's TestClient backend
switch, but Starlette actually uses the standard httpx library, not httpx2.
Update "httpx2>=0.1" to use the correct httpx package to properly address the
deprecation warning workaround as intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 03d9cf5b-14f9-425c-b96d-b10bfd698508

📥 Commits

Reviewing files that changed from the base of the PR and between dba7ac9 and d091884.

📒 Files selected for processing (2)
  • pyproject.toml
  • src/plane_conductor/runner.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/plane_conductor/runner.py

Comment thread pyproject.toml
Comment on lines +48 to +51
# starlette 1.0 switched its TestClient backend to httpx2; the deprecation
# warning hits strict-mode pytest at collection. Pinning httpx2 here lets
# TestClient pick it up without us touching production deps.
"httpx2>=0.1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if httpx2 exists on PyPI
echo "=== Checking httpx2 on PyPI ==="
curl -s https://pypi.org/pypi/httpx2/json | jq -r '.info.version // "PACKAGE NOT FOUND"'

echo -e "\n=== Checking httpx (the standard library) on PyPI ==="
curl -s https://pypi.org/pypi/httpx/json | jq -r '.info.version'

echo -e "\n=== Checking current Starlette dependency ==="
rg -n "starlette" pyproject.toml

Repository: volodchenkov/plane-conductor

Length of output: 301


🌐 Web query:

What HTTP client backend does Starlette TestClient use in version 1.0 and later?

💡 Result:

In Starlette version 1.0 and later, the TestClient uses the httpx library as its underlying HTTP client backend [1][2][3]. Prior to this version, Starlette's TestClient originally used the requests library. The transition from requests to httpx was implemented in Starlette 0.21.0 [4][5], ensuring that by the time Starlette reached its 1.0 stable release in March 2026, the TestClient was fully powered by httpx [4][5]. The TestClient provides an interface compatible with standard httpx sessions while adding specialized support for testing ASGI applications and WebSocket connections [2][3][6].

Citations:


Replace httpx2 with httpx—Starlette's TestClient backend is httpx, not httpx2.

Although httpx2 is a real package on PyPI, Starlette's TestClient uses the standard httpx library. The comment incorrectly states that Starlette 1.0 switched to httpx2; it actually uses httpx. Pinning the wrong package will not address the deprecation warning workaround as intended.

Diff
-    # starlette 1.0 switched its TestClient backend to httpx2; the deprecation
+    # starlette 1.0 switched its TestClient backend to httpx; the deprecation
     # warning hits strict-mode pytest at collection. Pinning httpx here lets
     # TestClient pick it up without us touching production deps.
-    "httpx2>=0.1",
+    "httpx>=0.23.0",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 48 - 51, The dependency pinned in the test
requirements is incorrect. Replace the package name from httpx2 to httpx in the
test dependencies section. The comment references Starlette 1.0's TestClient
backend switch, but Starlette actually uses the standard httpx library, not
httpx2. Update "httpx2>=0.1" to use the correct httpx package to properly
address the deprecation warning workaround as intended.

@volodchenkov

Copy link
Copy Markdown
Owner Author

Response to CodeRabbit findings

Accepted (commit d091884):

  1. _flush_pending now has a catch-all Exception arm with structured logging — the flush task is detached from any webhook caller, so swallowing without a log was a real gap.
  2. wait_idle post-kill drain now bounded by a 5s deadline with a warning on overflow — protects against a flushed spawn that itself blocks indefinitely.

Declined as false positive:

  1. "Restore SessionAlreadyRunningError mapping to skipped in webhook spawn handling"Runner.request_spawn contractually does NOT raise SessionAlreadyRunningError. The duplicate-triple case is exactly what it queues internally and returns {"status": "queued"} for. The "coding guideline" CodeRabbit cites refers to the old spawn()-based path the webhook used to call. Adding the except would be dead code; the matching test would assert a path that can't be reached. See runner.py request_spawn body — the if key in self._active: return {"status": "queued"} branch.

Unrelated CI fix in the same commit: starlette 1.x's TestClient imports httpx2 and emits a deprecation warning when falling back to httpx. With filterwarnings = ["error"] this becomes a collection error in CI. Pinned httpx2 in dev-deps.

@volodchenkov volodchenkov merged commit 791516b into main Jun 18, 2026
5 checks passed
@volodchenkov volodchenkov deleted the feat/queue-on-running branch June 18, 2026 18:56
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.

1 participant