Skip to content

v0.9.33: scheduled tasks, bun upgrade - #9339

Merged
waleedlatif1 merged 43 commits into
mainfrom
staging
Oct 25, 2026
Merged

waleedlatif1 merged 43 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 7 commits October 22, 2026 23:14
…#9331)

* feat(mothership): run unattended turns durably on the leased chat path

A scheduled job's occurrence must outlive the Sim process driving it. The
headless lifecycle the wake door uses takes no controller lease, so a pod
death leaves its run unrecoverable. An unattended turn now admits like a
typed send (user message, run and recovery intent in one commit under the
chat lock), runs detached from any HTTP response, and records a new
unattended_stream recovery kind. Takeover restores it non-interactive with
the secret authority it was admitted with, instead of the interactive
default, and the abandoned-run sweep, stream liveness and billing-admission
recording treat both durable kinds alike.

Durable start now also forwards a caller's billing-admission hook, so a
job can key its per-run budget on the run's billing request.

The integration test kills the turn's controller three times mid-run; it
fails on a headless implementation, on a sweep blind to the new kind, and
on a takeover that restores the interactive default.

* feat(db): add scheduled job occurrence columns and archive legacy jobs

Scheduled jobs return as Sim-owned schedules whose occurrences run as
durable chat turns. A job's run record (job_execution_logs) gains the
occurrence it runs (unique per schedule), its outcome and summary, and
the billing request its charges settle under; a schedule gains a per-run
spend cap. Indexes are built concurrently.

Every job row written for the retired Go runner is archived, never
deleted, so none can start once the tick claims jobs again. The archive
is a script migration committing per keyed batch, so it finishes on any
row count and resumes after a crash.

* feat(mothership): run scheduled jobs from the schedule tick

Behind MOTHERSHIP_JOBS_ENABLED (off by default, and to stay off in
production until unattended runs hold production effects for approval),
the schedule tick gains a job lane:

- Claim: each due job's occurrence is recorded (queued) and the schedule
  advances past it in one transaction, skipping locked rows, so two ticks
  record it once and a crash never leaves a stale claim. Missed
  occurrences collapse into one; cadence is never under 15 minutes; a
  one-shot job (no cron) runs once and completes; max_runs completes.
- Dispatch: the occurrence starts as an unattended durable turn in the
  job's chat under its occurrence id (UUIDv5 of schedule and instant), so
  every retry finds the turn it admitted. A busy chat skips the
  occurrence; a payer over its plan limit skips it; an owner who lost
  access turns the job off. Only secrets a person named for the job mount.
- Reconcile: a queued occurrence whose dispatch was lost is dispatched
  again after two minutes; a running one whose turn ended is finalized;
  one past six hours is stopped.
- Finalize: exactly once under a row lock. A reported outcome is kept; a
  turn that ended well without one is not_met, summarized by its last
  reply. Three failed or interrupted runs in a row turn the job off and
  email its people; a met until_complete job completes.
- Budget: each billing callback records the run's cumulative settled cost
  on its record (keyed by the run's billing request), and a run at its
  cap pauses at its next step with a job_run_budget card. The cost is
  cumulative per billing request, which recovered legs reuse, so the cap
  survives worker hand-offs and Sim takeovers.

* improvement(mothership): run the job lane unflagged behind one indexed probe

* improvement(mothership): simplify the job lane and stop jobs through the shared Stop delivery

* chore(db): format the scheduled job migration metadata

* improvement(mothership): keep the durable-run predicates out of the chat protocol graph

* fix(mothership): settle job runs before claiming, keep spend-cap links and failure counts durable

* fix(mothership): stop orphaned job runs at their limit and skip any number of deleted occurrences

* fix(mothership): end a running job record whose run was removed
* docs(library): update best-ai-agents-support-ticket-triage

* Pi Babysit: address PR #9337 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
* docs(library): update dify-alternatives

* Pi Babysit: address PR #9336 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
Co-authored-by: Sim Pi Agent <pi@sim.ai>
* fix(workflow-tests): keep the error of a mock that throws

A mock that throws reaches the server as { runId, callId, error }. The reply
schema tried the output branch first, and its optional `output` matched,
stripping `error`: the block failed with "A mocked block must return an
object" instead of the thrown message, so a workflow that branches on its
error text behaved differently under test than live. The error branch is now
tried first.

* fix(workflow-tests): never read a mock error past the cap as an empty output

An error longer than the 10,000-character cap failed the error branch and
fell through to the output branch, which still read it as an empty output.
The output branch is now strict, so such a reply is refused instead.

* test(workflow-tests): drop the case that restates the reply schema
…9334)

* docs(library): update ai-agent-examples-by-department-and-industry

* Pi Babysit: address PR #9334 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
Bun 1.4.4 ships the updated root certificate store (oven-sh/bun#46159),
so Bun's native fetch stops trusting the distrusted emSign ECC Root CA - G3.
Moves the packageManager pin, engines floors, CI setup-bun versions, the
oven/bun base images, and the Pi sandbox Bun pin together.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner October 23, 2026 18:48
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Oct 25, 2026 6:44pm UTC

Request Review

@github-actions github-actions Bot added the requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep label Oct 23, 2026
@github-actions

Copy link
Copy Markdown

⚠️ Cross-repo companion check

One or more companion PRs aren't merged into main yet (aggregated across the feature PRs in this release). Merging this without them will leave copilot and sim out of sync — merge them in lockstep.

  • ⚠️ simstudioai/mothership#818 — merged into staging (this PR targets main) — feat(worker): run scheduled job turns as automated chat turns

@greptile-apps

greptile-apps Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[High impact] The PR reaches scheduled automation, workflow execution, stored credentials, and public library pages.

The new scripts must follow the repository’s logging requirement before merging.

Findings

  1. P2 Scripts bypass the required logger ▶

Summary

This release adds durable scheduled chat turns and upgrades Bun. Later changes add remote sandbox workers, separate auth and billing database pools, optional encryption for account tokens, smaller execution reads, and richer library links and Markdown routes.

  • Scheduled jobs run as recoverable chat turns.
  • Workflow code can run on a separate worker host.
  • Library articles gain more paths for readers and crawlers to find them.
  • Library articles update their guidance and examples.
  • OAuth account tokens can be sealed in the database.
  • Workflow inputs and service integrations keep compatible behavior.
  • Billing and auth queries use separate database pools.
  • Build and deployment setup uses Bun 1.4.4.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Tick[Schedule tick] --> Claim[Claim occurrence]
  Claim --> Dispatch[Lock and dispatch]
  Dispatch --> Admit[Save chat turn and recovery intent]
  Admit --> Worker[Run unattended turn]
  Worker --> Reconcile[Reconcile and finalize]
  Reconcile --> Schedule[Update job schedule]
  App[App execution pool] --> Host[Remote sandbox host]
  Host --> Sandbox[Worker child process]
Loading

Reviews (2) · Last reviewed commit: "docs(library): update best-ai-agents-for..." · Reviewed by Greptile

Comment thread apps/sim/lib/mothership/jobs/finalize.ts
Comment thread apps/sim/lib/mothership/jobs/runs.ts
Comment thread apps/sim/lib/mothership/jobs/lane.ts

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 84 files

Heads up: you’re close to your flex budget. Increase your flex budget so reviews don’t pause.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/lib/mothership/jobs/reconcile.ts Outdated
Comment thread packages/db/migrations/0437_scheduled_job_occurrences.sql
Comment thread apps/sim/lib/mothership/request/lifecycle/unattended.ts
Comment thread apps/sim/app/api/billing/update-cost/route.ts
Comment thread apps/sim/lib/mothership/jobs/budget.ts
Comment thread apps/sim/lib/mothership/jobs/brief.ts
Comment thread apps/sim/app/api/schedules/execute/route.ts
Comment thread apps/sim/app/api/billing/update-cost/route.ts
Comment thread apps/sim/lib/mothership/jobs/finalize.ts Outdated
OpenAI retired gpt-4.1-nano and the API now returns 404, so move it from legacy to deprecated: hidden from model pickers, still a known id so pinned workflows validate.
…covery (#9341)

An MCP server whose hostname no longer resolves made tool discovery answer 500 and log at error. McpDnsResolutionError is now a server-unavailable condition (discover 422, v2 503), and categorizeError maps it to 502 and egress/domain refusals to 403, matching test-connection and server lifecycle.
…ery poll (#9343)

Gmail answers history.list with 404 once the stored cursor ages out of its retained history and the client must full-sync. The fallback searched the inbox, then took the mailbox profile's historyId as the new cursor, but a quiet mailbox reports the expired id itself, so it was persisted again and every poll repeated the 404, search and profile calls with an ERROR log. The resync now never adopts the rejected cursor and drops it when nothing newer exists, so the webhook polls through search until matching mail supplies one. The expected 404 logs at info and other history errors at warn.
…9344)

* fix(logs): keep provenance sidecars intact through log PII redaction

Log-stage PII masking walks every string leaf of the execution state, so a
match inside an encrypted provenance entry rewrote the ciphertext and the
display read failed closed with the final output withheld. Restore the
finalOutput/workflowInput sidecars, loop/parallel scope provenance, and
sourceExecutionId from the pre-redaction state alongside the fields already
restored. An undecryptable stored envelope now logs one attributed error per
read instead of three.

* test(provenance): expect provenance imports to decrypt without the helper's own failure log
… input types (#9342)

* fix(workflows): accept json input type and return 400 for unsupported input types

A Start/API trigger input field typed "json" (the name blocks use for objects) made manual and copilot runs fail with a 500, and broke MCP schema sync on deploy, because the shared run schema threw a plain Error on any type outside its list. Treat json as object and files as file[] everywhere the type is read, raise OrchestrationError('validation') for reserved names and unsupported types so callers answer 400 naming the field and allowed types, and canonicalize or reject input-format row types at write time.

* fix(workflows): sample aliased input types as their canonical type in mock payloads

* fix(mcp): answer an unpublishable workflow input as a validation failure
* fix(mothership): close the scheduled-job review gaps

- dispatch one attempt at a time per occurrence, so a skip or block never closes a live run's record
- brief each occurrence with the run number fixed at its claim, not the job's moving count
- report an automatic turn-off only when this failure turned the job off, not one a person already turned off
- mark a deadline stop after it is delivered, so a crash before then delivers it again
- state the run's spend cap only where hosted billing enforces it
- recover an invalid index left by an interrupted build in the job migration, as 0435 does
- give PlanUsageLimitDisplay a props interface

* fix(mothership): record why a run stops before delivering the stop, and redeliver one an earlier pass did not finish
…loy-check transaction early, cache integration tool schemas (#9347)

* fix(db): give billing and auth their own connection pools

postgres.js pipelines new queries onto busy connections when none is idle,
so slow reads saturating the process pool made short usage-ledger
transactions wait seconds for BEGIN until Postgres killed them.

- Add `billing` (4) and `auth` (3) dbFor roles, with DATABASE_URL_BILLING
  and DATABASE_URL_AUTH overrides falling back to the process URL.
- Billing pool: recordCumulativeUsage, recordUsage without a caller tx,
  the execution logger's usage reconciliation, both threshold settlement
  transactions, and recordJobRunSpend. Each moved transaction only queries
  through its tx handle; readers stay on the process pool.
- Auth pool: getAuthPool() backs the Better Auth adapter, getAuthDatabase()
  outside a transaction, the OAuth adapter guard defaults, and the API-key
  hash lookup.
- isPrimaryPoolClient() in @sim/db replaces the `executor === db` checks in
  the security, session, and SSO policy caches, so reads through the auth
  pool keep hitting them instead of costing two queries per cached session.
- Integration test saturates the process pool and requires the billing
  write, an adapter session read, and an API-key lookup to finish anyway.

* fix(workflows): compare deployment states after the snapshot transaction commits

checkNeedsRedeployment ran the full workflow diff inside its REPEATABLE READ
transaction, holding a pooled connection idle in transaction during CPU work.
Reads (active version, normalized draft, variables, credential migrations)
stay in the snapshot; the pure diff now runs after commit.

* fix(mothership): build integration tool schemas once per process instead of per user every 5s

The integration catalog cached each viewer's full schema set for 5s, so nearly
every call rebuilt a user-tool schema for all ~5.3k exposed tools, sized the
~7.7MB result with JSON.stringify, structuredClone'd it, and re-tokenized
every tool for BM25 - all synchronously.

The schemas depend only on the registry and deployment config, so they are now
built once per (surface, personal mode), frozen, and shared; the build yields
between tools. Each request still applies the viewer's own block visibility and
permission policy, and the plan-dependent email tagline is read per user with
the same 5s TTL. Results are byte-identical. BM25 term counts are memoized per
shared schema object.

* refactor(db): tighten pool-isolation follow-ups

Assert each frozen-schema mutation throws, rank BM25 by query terms instead of
copying each tool's vocabulary, gate the reporting usage cache on
isPrimaryPoolClient like the auth caches, and trim comments.

* fix(api-key): read workspace key settings on the auth pool

Give the memory harness integration mock a dbFor so the billing pool resolves.

* test(db): close every pool the isolation suite opens
)
process.exit(1)
}
console.log(`✓ sealed columns (${columns.map((column) => column.reference).join(', ')})`)

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.

P2 Scripts bypass the required logger

The new audit writes results and errors through console.log and console.error. The repository requires createLogger from @sim/logger and forbids console.log. The new ping-indexnow.ts script also uses direct console output.

Use the shared logger in both scripts before merging to satisfy this repository requirement.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@cubic-dev-ai cubic-dev-ai Bot 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.

2 issues found and verified against the latest diff

Confidence score: 3/5

  • isolated-vm-host.cjs can buffer up to 64 MiB per unauthenticated socket across as many as 4 * MAX_WORKERS pending connections, creating a host-memory exhaustion risk before token validation. Keep the pre-authentication frame limit small.
  • executeInIsolatedVM resolves the AppConfig-backed worker target before checking signal.aborted, so an already-cancelled call can wait on AWS configuration instead of promptly rejecting with AbortError. Check cancellation first.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/lib/execution/isolated-vm.ts">

<violation number="1" location="apps/sim/lib/execution/isolated-vm.ts:1498">
P2: `executeInIsolatedVM` resolves the AppConfig-backed worker target before checking `signal.aborted`. An already-cancelled call can therefore wait on AWS configuration instead of returning `AbortError` immediately; check the signal first, then activate the target.</violation>
</file>

<file name="apps/sim/lib/execution/isolated-vm-host.cjs">

<violation number="1" location="apps/sim/lib/execution/isolated-vm-host.cjs:156">
P2: The unauthenticated phase accepts the full 64 MiB `maxPayload` on up to `4 * MAX_WORKERS` pending sockets, so an attacker can exhaust host memory before presenting a token. Authenticate through a small-frame path or enforce a much smaller limit until authentication succeeds, then retain the large limit for worker messages.</violation>
</file>

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Turn on auto-fix | Re-trigger cubic

Comment thread .github/workflows/ci.yml
Comment thread apps/sim/app/(landing)/comparisons/library-links.ts Outdated
Comment thread packages/db/pool-profiles.ts
Comment thread packages/testing/src/mocks/database.mock.ts
req: IsolatedVMExecutionRequest,
options?: IsolatedVMExecutionOptions
): Promise<IsolatedVMExecutionResult> {
activateWorkerTarget(await resolveWorkerTarget())

@cubic-dev-ai cubic-dev-ai Bot Oct 24, 2026 •

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.

P2: executeInIsolatedVM resolves the AppConfig-backed worker target before checking signal.aborted. An already-cancelled call can therefore wait on AWS configuration instead of returning AbortError immediately; check the signal first, then activate the target.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/execution/isolated-vm.ts, line 1498:

<comment>`executeInIsolatedVM` resolves the AppConfig-backed worker target before checking `signal.aborted`. An already-cancelled call can therefore wait on AWS configuration instead of returning `AbortError` immediately; check the signal first, then activate the target.</comment>

<file context>
@@ -1402,6 +1495,7 @@ export async function executeInIsolatedVM(
   req: IsolatedVMExecutionRequest,
   options?: IsolatedVMExecutionOptions
 ): Promise<IsolatedVMExecutionResult> {
+  activateWorkerTarget(await resolveWorkerTarget())
   const ownerKey = normalizeOwnerKey(req.ownerKey)
   const ownerWeight = normalizeOwnerWeight(req.ownerWeight)
</file context>
Fix with cubic

Comment thread apps/sim/lib/workflows/input-format-schema.ts
Comment thread apps/sim/lib/mothership/jobs/claim.ts
Comment thread apps/sim/lib/webhooks/polling/gmail.ts Outdated

const wss = new WebSocketServer({
noServer: true,
maxPayload: MAX_MESSAGE_BYTES,

@cubic-dev-ai cubic-dev-ai Bot Oct 24, 2026 •

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.

P2: The unauthenticated phase accepts the full 64 MiB maxPayload on up to 4 * MAX_WORKERS pending sockets, so an attacker can exhaust host memory before presenting a token. Authenticate through a small-frame path or enforce a much smaller limit until authentication succeeds, then retain the large limit for worker messages.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/execution/isolated-vm-host.cjs, line 156:

<comment>The unauthenticated phase accepts the full 64 MiB `maxPayload` on up to `4 * MAX_WORKERS` pending sockets, so an attacker can exhaust host memory before presenting a token. Authenticate through a small-frame path or enforce a much smaller limit until authentication succeeds, then retain the large limit for worker messages.</comment>

<file context>
@@ -0,0 +1,376 @@
+
+const wss = new WebSocketServer({
+  noServer: true,
+  maxPayload: MAX_MESSAGE_BYTES,
+  perMessageDeflate: false,
+})
</file context>
Fix with cubic

Comment thread apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.ts Outdated
…set their mock between tests (#9365)

- The self-hosting scaling guide budgets every app subpool: add the billing (4)
  and auth (3) pools and correct a web process's ceiling to 37 primary
  connections.
- resetDbChainMock now restores the shared isPrimaryPoolClient spy, so an
  override in one test cannot leak into the next; the per-file afterEach that
  did this by hand is gone.
…9364)

A search-mode poll (no stored cursor) sent historyId: undefined, which updateWebhookProviderConfig treats as a key removal. If it overlapped a newer poll that had just stored a cursor, it deleted that cursor. The poll state is now built once and carries historyId only when this poll started with a cursor or found one, so only a poll whose cursor Gmail rejected can clear it.
… is configured (#9363)

Stripe's verifier accepts signatures computed with an empty key, so the Better Auth Stripe plugin now registers only when billing is on and both STRIPE_SECRET_KEY and a non-blank STRIPE_WEBHOOK_SECRET are set, logging an error otherwise. The sim-setup preflight requires the webhook secret alongside the secret key.
…change (#9370)

Production and staging keep building every affected commit, so a shared-package change that breaks the docs build still fails on staging before main. Other preview branches skip the build unless apps/docs changed since the branch's last successful deployment; a first deployment or a previous SHA outside the shallow clone builds.
… of skipping the run (#9366)

* fix(mothership): retry a job dispatch whose usage read failed instead of skipping the run

- a usage outage (`usage_unavailable`) is a failed dispatch attempt, retried and bounded like any other, not a spent limit
- a run skipped for billing records billing's own reason
- the job lane suite closes open connections before stopping its fake worker

* test(mothership): cover a job dispatch retried after a usage outage and ended at its last attempt
…alidation, and match bare-name alternatives titles (#9367)

* fix(mcp): answer an unpublishable workflow input on tool updates as validation, and match bare-name alternatives titles

Updating a workflow MCP tool's parameter descriptions regenerated its schema without the validation translation the create and add paths use, so a reserved workflow input name returned an internal error instead of a validation failure naming the field. Both paths now share one helper.

The comparison library links scored a title naming a competitor's alternatives only through its mention aliases, which leave out a bare common-word name such as Make. The bare name now counts directly before "Alternatives", so a "Make Alternatives" title ranks first without reading "Make" elsewhere in a title as the competitor.

* test(library): type the comparison fixture's tags so the cast to ContentMeta type-checks
…he-top-options-compare (#9368)

* docs(library): update agentic-ai-coding-tools-what-they-are-and-how-the-top-options-compare

* Pi Babysit: address PR #9368 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
* docs(library): update openai-vs-n8n-vs-sim

* Pi Babysit: address PR #9369 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
)

* fix(mothership): hold callerless runs' sim_cli to read API scopes

An unattributed inbox message runs as the workspace owner with its tool
permission capped to read, but sim_cli has no catalog permission and its
commands authorize the delegated subject's live role, so the CLI could
create, run, and delete with the owner's authority.

A run with no caller of its own now executes the CLI inside a delegated
API-scope grant of api:read, enforced in requireOAuthOperationScope the
same way an OAuth token's scopes are, so every authorization path the
CLI reaches refuses api:write operations.

* fix(mothership): refuse callerless service writes and organization-scoped CLI writes

Mixed server tools reached through sim_cli services (settings, dashboards,
tests, search_sources, workspaces) write through internal operations that
declare no API scope, so the read grant never bound them. Each tool now
declares which validated arguments write, and the router holds those to
the current delegated grant.

The grant also applies to callerless organization-scoped runs.

* test(mothership): model attributed CLI runs with their secret actor
…he-top-options-compare (#9373)

* docs(library): update agentic-ai-coding-tools-what-they-are-and-how-the-top-options-compare

* Pi Babysit: address PR #9373 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…d Engineering Teams Choose? (#9375)

Co-authored-by: Sim Pi Agent <pi@sim.ai>
@waleedlatif1
waleedlatif1 merged commit 6154482 into main Oct 25, 2026
204 of 206 checks passed

This branch was previously deployed

1 inactive deployment
Preview — 09d3bef7 Deployed Oct 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant