The check-config-files job blocked the strands-crm showcase PR
(examples/showcases/strands-crm/frontend/next.config.ts was not in
the build-config allowlist). Adding it next to strands-file-analyzer.
## Problem
CLI starters for Mastra, Agno, PydanticAI, AWS Strands, Google ADK, MS
Agent Framework, and LlamaIndex fail on `npm run dev` with:
```
Error: dependency failed to start: container copilotkit-intelligence-intelligence-1 is unhealthy
intelligence-1 | error: database "intelligence_app" does not exist
```
## Root cause
The overlay compose relied on a bind-mounted `docker/init-db/` script to
create `intelligence_app`, but the CLI's Intelligence activation copies
**only** `docker-compose.yml` into scaffolded projects. The mount source
never exists there, so Docker creates it as an empty directory, postgres
initializes with only the default DB, and the composite's migrations
fail.
`langgraph-python-threads` was unaffected because its template ships its
own compose + `docker/init-db/` in-repo.
## Why it keeps biting internal testers
The compose project name is fixed (`copilotkit-intelligence`), so
**every starter on a machine shares one `postgres-data` volume** — and
postgres only honors `POSTGRES_DB`/init scripts on first init of an
*empty* volume. One broken run poisons the volume for every subsequent
starter, regardless of framework.
## Fix (self-contained + self-healing)
- `POSTGRES_DB: intelligence_app` and drop the `docker/init-db` bind
mount + script (the shadow DB it also created is only used by repo-local
dev tooling, never by the composite)
- Add an idempotent `provision-db` one-shot (same pattern as the
existing `provision-user`) that creates `intelligence_app` when missing
— this heals already-poisoned volumes on the next `docker compose up`,
with no manual `down -v`
- Gate `intelligence` on `provision-db` completion; align the postgres
healthcheck dbname
Because the CLI fetches this overlay live from `main` at scaffold time,
merging this fixes every CLI version in the wild immediately — no CLI
release needed.
## Verification
Tested with real Docker on isolated compose projects:
1. **Poisoned volume** (reproduced by running the old compose revision
with the empty `init-db` dir the CLI leaves behind → volume with no
`intelligence_app`): new compose → `provision-db` exits 0, migrations
run, composite **Healthy**, `up -d --wait` exits 0
2. **Fresh volume**: DB created via `POSTGRES_DB`, `provision-db`
no-ops, composite **Healthy**
3. **Idempotency**: second `up -d --wait` on a running stack exits 0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Why
New users are still finding cloud.copilotkit.ai even though they
shouldn't be. An org-wide audit found the live funnels; this PR closes
every source in this repo.
## What changed
Replaced all **user-facing web links** to `https://cloud.copilotkit.ai`
with `https://dashboard.operations.copilotkit.ai` (the same destination
the copilotkit.ai marketing CTAs already use):
- **Live docs pages** (`docs/` + `showcase/shell-docs/`) —
premium/observability/threads/oss-vs-enterprise/intelligence-platform
pages, integration quickstarts/tutorials, V1 reference pages, shared
snippets
- **`LinkToCopilotCloud` component** (both copies) — was routing through
`go.copilotkit.ai/copilot-cloud-button-docs` → cloud sign-in; now points
at the dashboard directly
- **README** — the `?ref=github_readme` CTA (label updated to "Try the
Enterprise Intelligence Platform")
- **Example READMEs** (`examples/v1/*`, showcases)
- **Shipped package code** so future releases stop reseeding the
problem: `console-styling.ts` key warnings, `usage-banner.tsx`,
`developer-console-modal.tsx`, V2 `license-warning-banner` (React +
Vue), provider JSDoc
## Deliberately untouched
- `api.cloud.copilotkit.ai` endpoints (`COPILOT_CLOUD_API_URL`,
check-for-updates, telemetry tests) — existing cloud customers depend on
these
- "Copilot Cloud" prose/product naming in docs copy — link targets only;
copy rewrite is a separate editorial pass
## Out of repo (follow-ups, not in this PR)
- `go.copilotkit.ai/GetStarted` still 302s to cloud — zero code
references org-wide, so traffic is from old emails/posts; needs
repointing at the shortener
- Already-published npm versions carry the old links forever — only a
cloud-side signup gate closes that vector
## Verification
- `nx run-many -t test,build -p @copilotkit/shared
@copilotkit/react-core @copilotkit/vue` ✅
- `@copilotkit/sqlite-runner:test` ✅ (failed initially from a stale
Node-20 `better-sqlite3` binary in the fresh worktree, unrelated to this
change)
- Zero remaining non-`api.` `cloud.copilotkit.ai` references in
docs/packages/examples
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The activation overlay's docker-compose.yml relied on a bind-mounted
docker/init-db script to create the intelligence_app database, but the
CLI copies only docker-compose.yml into scaffolded projects. The mount
source never exists there, Docker creates it as an empty directory,
postgres initializes without intelligence_app, and the composite's
migrations fail: 'database "intelligence_app" does not exist' →
'dependency failed to start: container ...intelligence-1 is unhealthy'.
Make the compose file self-contained:
- Set POSTGRES_DB: intelligence_app (drop the init-db bind mount and
the now-unused init script; the shadow DB it also created is only
used by repo-local dev tooling, never by the composite).
- Add an idempotent provision-db one-shot that creates the database
when missing. POSTGRES_DB only applies on first init of an empty
volume, and the volume is shared across all scaffolds via the fixed
compose project name — so machines that already hit the bug have a
data volume without the database. The one-shot heals those on the
next 'docker compose up' with no manual 'down -v'.
- Gate the composite on provision-db completion instead of postgres
health, and align the postgres healthcheck dbname.
New users were still discovering cloud.copilotkit.ai through docs pages,
the README, example READMEs, and in-app banners/console messages. Replace
all user-facing web links with dashboard.operations.copilotkit.ai (the
destination the marketing-site CTAs already use). Functional API endpoints
(api.cloud.copilotkit.ai) are deliberately untouched since existing cloud
customers depend on them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Why
The langgraph-python template's `uv.lock` was stale: it locked
`copilotkit 0.1.87` / `ag-ui-langgraph 0.0.33` while `pyproject.toml`
pins `copilotkit==0.1.93`. Because the lock didn't match, every
scaffold's `uv sync` discarded it and re-resolved fresh against PyPI
latest.
That silent re-resolution is how fresh `copilotkit init` scaffolds
(langgraph-py / a2ui / opengenui) broke today: they picked up
`ag-ui-langgraph 0.0.39`, which imported a then-unpublished
`ag-ui-a2ui-toolkit` symbol and failed at import time (`ImportError:
cannot import name 'run_a2ui_generation_with_recovery'`). The other
python templates (langgraph-fastapi, langgraph-python-threads,
strands-python) survived precisely because their locks are in sync.
## What
Regenerated `uv.lock` with `uv lock` so it matches `pyproject.toml`. It
now locks the fixed pair published by the ag-ui maintainers:
`ag-ui-langgraph 0.0.40` + `ag-ui-a2ui-toolkit 0.0.2` (verified to
import cleanly and load the template graph). With the lock in sync,
scaffolds become deterministic — a future broken upstream release can no
longer break `copilotkit init` until the lock is deliberately updated.
Tracking the upstream release-process issue in OSS-256.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
Move the langgraph A2UI demos + docs off alphas now that the stable
packages are out.
### Deps (commit 1)
- `copilotkit` `0.1.94a3` → **`0.1.94`** (langgraph-python,
langgraph-fastapi)
- `@copilotkit/*` `1.59.2` → **`1.59.4`** + `@copilotkit/sdk-js`
`1.59.3-alpha.3` → **`1.59.4`** (langgraph-typescript + agent)
- **Dropped the `@ag-ui/langgraph` override** —
`@copilotkit/runtime@1.59.4` now carries `@ag-ui/a2ui-middleware@0.0.6`
(the `injectA2UITool` forward) + `@ag-ui/langgraph@0.0.37` (the state
surface). Locks regenerated; `npm ci` in sync.
### Docs (commit 2)
- Rewrote the **dynamic-schema** A2UI pages (langgraph + deepagents +
generic) for the middleware flow:
- **prebuilt agent** → just add `CopilotKitMiddleware`
- **graph agent** → wire `get_a2ui_tools`
- `injectA2UITool` is the single on/off switch
- Removed the stale hand-rolled `render_a2ui`/`generate_a2ui` +
`a2ui_prompt` framing; corrected streamed op names
(`createSurface`/`updateComponents`/`updateDataModel`).
## Why
The merged middleware PR made A2UI opt-in via the forwarded
`injectA2UITool` flag; the showcase was pinned to alphas + a transitive
override, and the docs still described the pre-middleware design.
Our previous doc site had a fumadocs/shadcn theme that was a bit
hodge-podge. This brings all of it into one clean and visually appealing
design. The border radii are standardized, colors are standardized and
bunch of passes for mobile/tablet have been done. An added bonus is that
the header nav has been redone to feel more natural to the eyes.
<img width="1850" height="1256" alt="Screenshot 2026-06-05 at 7 48
00 AM"
src="https://github.com/user-attachments/assets/0c6032f2-e406-4e51-9c31-d69a9d258d61"
/>
Applies the updated shell-docs theme treatment to the overview cards, sample tabs, framework selectors, hero command controls, and Copilot Cloud CTAs.
Call-site enumeration:
- FrameworkSelector/FrameworkTabs/IntegrationGrid changes stay on the docs landing and framework overview surfaces that already own those controls.
- OpsPlatformCTA and LinkToCopilotCloud keep their existing call sites while switching to shared chrome variables.
Refreshes the shell docs chrome around the shared theme tokens, announcement banner, desktop and mobile navigation, search trigger, theme toggle, and sidebar footer actions.
Call-site enumeration:
- PrimaryDocsTabs: rendered by MobileTopNav for tablet header tabs and ShellDocsLayout for mobile sidebar tabs.
- MobileSidebarFooterTalk: rendered by ShellDocsLayout sidebar.footer and hidden outside mobile/tablet sidebar usage via responsive classes.
- INTELLIGENCE_CTA_HREF and TALK_TO_ENGINEER_HREF: exported from BrandNav and reused by BrandNav/MobileSidebarFooterTalk so CTA destinations stay centralized.
Northstar AI CRM — a full-featured agentic sales CRM for an enterprise
hardware seller, built with CopilotKit, the AG-UI protocol, and a
TypeScript Strands agent (@strands-agents/sdk + @ag-ui/aws-strands).
The first TypeScript Strands example under examples/.
Highlights:
- Agentic canvas: tool calls render on the workspace — quote approval
opens a quote page; analyze_team and generate_weekly_report open
dedicated report pages; frontend tools navigate the workspace.
- Generative UI cards, human-in-the-loop follow-up approval, and a live
STATE_SNAPSHOT that syncs a SQLite CRM store with the board,
dashboard, and report pages.
- Dashboard, pipeline (Kanban), products, team, and reports surfaces.
Self-contained under examples/showcases/strands-crm with an agent/ +
frontend/ split and a concurrently dev script; listed in
examples/README.md.
## Summary
Makes the `examples/showcases/banking/` SaaS demo customer-ready by
migrating it to **CopilotKit v2** and polishing it into a credible,
reliable reference. Closes the non-memory gaps from FOR-133's assessment
(FOR-138).
- **v2 migration**: v2 Hono runtime route (`BuiltInAgent` +
`createCopilotHonoHandler` + `InMemoryAgentRunner`), `CopilotKit`
provider + `CopilotPopup`, hooks moved to `@copilotkit/react-core/v2`
(`useAgentContext`, `useHumanInTheLoop`, `useComponent`, Zod params), on
`workspace:*` packages.
- **File-backed data store**: domain data in `src/data/seed.json` behind
a typed `src/lib/store.ts` (in-memory, resets to seed on restart). Clean
seam left for the memory track (FOR-137).
- **Credible identity + data**: Northwind Finance / Alex Morgan /
`@northwind.example`, refreshed transaction dates + future card
expiries, `Intl` currency formatting.
- **Dropped** the SQL page + fake-MSA RAG feature (and committed
ServiceNow secret) — focused fintech story.
- **Framework/build**: Next 16 async route `params`, Tailwind v4 (so the
v2 stylesheet imports normally), correct `next`/`react` versions, single
lockfile, accurate v2 README.
- **Tests**: LLM-free Playwright smoke test (also fails on
hydration/uncaught errors).
## Verified live (real OpenAI key)
Full golden path works end-to-end: chat renders → "show transactions for
card 4242" renders the generative-UI list → "add a card" → HITL approval
card → Approve → new card appears in the grid. 0 console errors. Build +
typecheck + smoke test green; no committed secrets.
Three v2 chat-rendering bugs were found and fixed by running it live:
use the full `CopilotKit` provider (not the low-level
`CopilotKitProvider`, which omits `ThreadsProvider`); set
`useSingleEndpoint={false}` to match the multi-endpoint route; and use
`useComponent` with a `deps` array for display-only generative UI
(avoids a stale-closure empty render).
## Non-goals (deliberately out of scope)
- Real auth.
- Conversation threads + long-term/self-learning memory → FOR-137.
- Deleting the duplicate `enterprise-brex` demo + repointing its deploy.
## Test plan
- [ ] `pnpm --filter demo-saas-copilot build` succeeds
- [ ] `pnpm --filter demo-saas-copilot exec tsc --noEmit` clean
- [ ] `pnpm --filter demo-saas-copilot test:e2e` passes
- [ ] With `OPENAI_API_KEY` set: run the golden path (transactions
gen-UI, add-card HITL → approve → grid update)
## Known minor follow-ups
- Removing deprecated `showDevConsole` surfaces the v2 Web Inspector /
announcement banner.
- Newly-added cards get a near-term generated expiry.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
It was left at false while fastapi/typescript are true. Under the opt-in
A2UI model false means no tool is injected, so the python demo rendered
no surfaces (and the docs code-tab showed no injectA2UITool). Set true to
match the other langgraph integrations.
Treat `signal` as optional on legacy LiveBadge rows (neutral 'unknown' marker
when absent), so the trimmed initial projection cannot misrender or throw.
Fetch `signal` on demand for genuinely-failing badges (excluding staleness-
downgraded greens via fail_count), with per-badge loading/error resolution that
distinguishes in-flight, partial-failure (requested-but-absent), and resolved
states. Injection-safe pb.filter OR query; object-valued fields render as JSON.
Banner visibility still derives from projection-safe key+state on the live rows;
detail copy self-fetches `signal` (via pb.filter placeholders, page size = key
count) only when omitted by the trimmed projection, with an identity-tagged cache
(id + transitioned_at) that won't serve a stale signal across content changes.
Thread a `stale` flag (muted, non-authoritative counts during reconnect-with-rows,
distinct from the no-rows loading state), fix the amber-chip/green-D5 cell to
classify into the health dimension, and surface the hook's `degraded` flag in the
header LiveIndicator (offline outranks degraded). Wire `degraded` from
useLiveStatus through to FeatureGrid in dashboard-page.
Drop the heavy `signal` field from the initial PocketBase fetch via a
STATUS_LIST_FIELDS projection + skipTotal + length-based bounded-concurrency
pagination, restoring fast first paint. Harden the realtime path: flushPending
teardown guards, keyless-delete id->key buffer collapse (last-write-wins),
SSE-delta signal-presence upsert, and a `degraded` flapping flag that clears on
heartbeat-success and resets on terminal error. ConnectionStatus deduped.
## Summary
The coverage dashboard's initial load was slow and briefly rendered
misleading data. Root cause: `useLiveStatus` fetched the entire
(growing) PocketBase `status` collection via a single `getFullList`,
which PocketBase serial-paginates at perPage≤500 — ~5 sequential
blocking round-trips before first paint. During that window the grid
rendered authoritative `D0` cells and `✓0 ~0 ✗0` column tallies (not a
loading state), and a transient amber-D5/D6 toggle flash appeared
pre-hydration.
## Changes
- **perf:** concurrent paged fetch — page 1 then `Promise.all` over
pages 2..N, merged by array index (deterministic regardless of
resolution order), with a stable `sort: "id"` on every page so
concurrent requests share one ordering. Initial `setRows` wrapped in
`startTransition` to avoid a synchronous full-matrix re-render.
Best-effort snapshot; the live SSE subscription reconciles anything
created in the brief fetch→subscribe window (PocketBase `id` is random,
not monotonic — no growth-completeness claim).
- **fix:** `connecting` + empty-map now renders a loading affordance
instead of authoritative zeros (the §5.3 "no stale-green/fake-zero lie"
guarantee); `loading` made required on the tally type; fail-safe
undefined-tally fallback defaults to loading rather than zeros.
## Test plan
- [x] Order-safety: pages merge deterministically even when page 1
resolves last (out-of-order resolution)
- [x] Stable sort forwarded to every page request (behavioral — mock
sorts before slicing)
- [x] Loading-state quadrants on both `computeColumnTally` and
`computeColumnTallyDetail` (connecting+empty → loading; connecting+rows
→ authoritative; live+empty → authoritative; error → offline)
- [x] `OverlayColumnHeader` renders loading/offline affordance, not zero
glyphs
- [x] Full dashboard suite: 827 passed / 1 skipped; tsc clean; next
build green
Restore stubbed globals (EventSource, localStorage, window config) in
afterEach via vi.stubGlobal/unstubAllGlobals + snapshot, and unmount the
hook in a finally so the 30s heartbeat interval and its pb singleton don't
leak past the test onto the about-to-close in-process server.
Also normalize the fetchInitial page merge to
[first.items, ...rest.map((r) => r.items)].flat() for clarity (behavior
identical to the prior heterogeneous flatMap).
The PocketBase JS SDK derives a request key from method+path and
auto-cancels any in-flight request that shares it. useLiveStatus's
fetchInitial fans pages 2..N out concurrently at the same path
(/api/collections/status/records), so every page after the first
cancelled its predecessor — the cancelled promises rejected, Promise.all
rejected, and the hook dropped to OFFLINE. Pass requestKey:null on the
page-1 read, every fan-out page, and the heartbeat ping so all
concurrent same-path reads complete.
Add a real-SDK regression test (the mocked suite never exercised the SDK
so it missed this): an in-process http server replays paged status
records with per-page latency, the production getPb() client drives the
real hook, and it asserts "live" with all rows. RED without requestKey:null.
A connecting status with an empty status map now renders a loading
affordance instead of authoritative checkmark-0 / D0 cells and
zeroed column tallies. Make loading required on the tally type so the
contract is explicit, and default the fail-safe undefined-tally
fallback to loading rather than fake zeros. Adds coverage for the
loading-state quadrants across computeColumnTally /
computeColumnTallyDetail and the OverlayColumnHeader affordance.
Replace the single serial-paginating getFullList with a concurrent
page-1 fetch + Promise.all fan-out over the remaining pages, merged by
array index so results are deterministic regardless of resolution
order. Forward a stable sort:"id" on every page so concurrent requests
share one ordering. Wrap the initial setRows in startTransition to
avoid a synchronous full-matrix re-render. The snapshot is best-effort;
the live SSE subscription reconciles anything created in the brief
fetch->subscribe window.
Previously this job would run on main and give pretty poor signal into
non-serious failures. We only really want this logic on PR's because
otherwise CI becomes red for non-urgent issues.
PRs will continue to run this action and fail on quality failures (like
lint or commitlint issues)
## Release monorepo v1.59.5
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.59.5`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.59.5`
- Creates git tag `monorepo/v1.59.5`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
A STARTER-row column for an integration that has NO starter (the 7 columns
absent from STARTER_TO_COLUMN) was rendering a grey "✗" with a "no starter
for this integration" tooltip. That mis-communicates the truth: a grey/red
✗ reads as "we expected data and got none" or "a probe ran and failed",
when the reality is "this integration is architecturally unsupported in the
starter row".
buildStarterBadge's mapping-derived !isSupported branch now returns the same
🚫 "Not supported by this framework" treatment depth-chip/unified-cell
already use for unsupported cells. Columns that DO have a starter keep
surfacing their real probe status — a genuinely-red starter still renders
red ✗ and is never masked as 🚫.
## What does this PR do?
Adds the **client-side renderer for the A2UI error-recovery status**
(OSS-162) — the third of the three A2UI "shipped" gaps from the recon
(robust error recovery).
The A2UI middleware emits an `a2ui_recovery` activity as a pure **data
contract** over the AG-UI event stream — `{ status: "retrying" |
"failed" | "resolved", attempt?, errors?, attempts?, error? }`. This PR
turns that into UX in `@copilotkit/react-core`, with no coupling to
`@ag-ui/a2ui-toolkit`:
- **`createA2UIRecoveryRenderer(options)`** (new —
`packages/react-core/src/v2/a2ui/A2UIRecoveryRenderer.tsx`), a
`ReactActivityMessageRenderer` for `activityType: "a2ui_recovery"`:
- **`retrying`** → a non-disruptive, collapsed *"Retrying UI
generation…"* hint that only appears once it would be perceptible (after
`showAfterMs`, or once `attempt` ≥ `showAfterAttempts`) — so a fast
first retry never flashes. It never replaces the surface or shows noisy
errors inline.
- **`failed`** → a clean, tasteful hard-failure message for the end
user, with structured developer detail (per-attempt errors) tucked into
an expandable `<details>`.
- **`resolved`** / anything else → renders nothing (the A2UI surface
renderer owns the UI on success).
- **Auto-registered** in `CopilotKitProvider`
(`builtInActivityRenderers`, gated on `runtimeA2UIEnabled`), alongside
the existing A2UI surface / skeleton / tool-call renderers — so apps
don't wire it up.
- **Configurable** via the provider's `a2ui.recovery` option
(`A2UIRecoveryRendererOptions`): `showAfterMs` (default 2000),
`showAfterAttempts` (default 2), `debugExposure` (`"hidden" |
"collapsed" | "verbose"`, default `"collapsed"`).
Timing and how much debug detail to surface are intentionally **client**
concerns, keeping react-core decoupled from the toolkit (the contract is
plain data).
Scope is deliberately small — 4 files, all in `react-core`: the new
renderer + 7 unit tests, the provider registration, and the export. The
recovery loop, the server-side validation gate, and the framework
adapters live in the **ag-ui** repo (separate PR — see below).
## Related PRs and Issues
- Linear: **OSS-162** — [Google] Add A2UI recovery / error-handling loop
- Server-side counterpart (shared `@ag-ui/a2ui-toolkit` validate +
retry, the `@ag-ui/a2ui-middleware` paint gate, the LangGraph TS/Python
adapters, and the dojo showcase + e2e): **ag-ui-protocol/ag-ui#1858**
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] If the PR changes or adds functionality, I have updated the
relevant documentation _(docs to follow once the cross-repo feature
lands)_
- [x] "Allow edits by maintainers" is checked
🤖 Generated with [Claude Code](https://claude.com/claude-code)
onReady fired after the first processMessages, but a data-bound list paints
nothing until its data model arrives — so at high stream latency the skeleton
dropped ~1s before the first card (blankness). Gate onReady on the surface
actually being renderable: for data-bound surfaces (components reference data via
`path`), wait for the first non-empty updateDataModel; static surfaces are
renderable from components alone. Latency-independent — fires exactly when the
first card can exist. Fallback timer bumped to 8s (backstop only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fixed cross-over delay can't be right — the time from ops-arrive to first card
varies with stream latency, payload size, and machine speed. Instead, the surface
processor fires onReady the moment it has processed its first operations; the
renderer swaps one animation frame later. The timer is demoted to a 1.5s safety
fallback. Latency-independent true replacement, no magic number to tune.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the recurring "mutation result not validated" defect class in the
starter-fleet provisioner and make the existing-services snapshot fail loud
instead of silently feeding erroneous create decisions.
- Add a uniform assertMutationOk guard and route EVERY mutation through it:
serviceCreate (assert .id), serviceInstanceUpdate (was DISCARDED — a false
Boolean! return meant sleep/healthcheck/image/creds were never applied while
the script reported success; now asserted and "configured" is logged only
after verification), serviceDomainCreate (assert .domain on the create path),
and serviceInstanceRedeploy (routed through the same guard for consistency).
- Absorb a serviceCreate "already exists" rejection: on a snapshot-miss the
create path now re-fetches the service id by name and falls through to UPDATE
instead of aborting the whole fleet. Predicate renamed ALREADY_EXISTS_RE and
reused by the domain-create path.
- fetchExistingServices fails loud on page-drain truncation (hasNextPage still
true at the defensive bound) rather than returning a partial byName map.
- fetchExistingServices coalesces null serviceInstances/.edges (transitional
service nodes) so an unguarded .find can't TypeError and abort the fetch;
interface fields marked optional/nullable.
- TRANSIENT_ERROR_RE made single-line ([^\n]*? not [\s\S]*?) so a newline-joined
multi-error blob can't bridge "Service" and "not found" across lines.
- withRetry wraps the schedule-exhaustion rethrow with context and { cause }.
Three functional fixes to the starter-fleet provisioner found in CR:
- fetchExistingServices now drains the Relay ServiceConnection via
pageInfo.hasNextPage/endCursor. A single un-paginated query truncated the
snapshot (~27 SSOT + 12 starter services span >1 page), making an existing
starter look absent → CREATE path → serviceCreate "already exists" →
non-transient abort of the whole run.
- TRANSIENT_ERROR_RE now matches Railway's INTERPOLATED "Service <id> not
found" (id embedded), not just the contiguous "Service not found", so the
post-create eventual-consistency retry actually fires.
- serviceInstanceRedeploy result check: documented the verified Boolean!
contract (sources: redeploy-env.ts, bin/railway RestoreCommand) and now
gates on truthiness (rejects false/null, accepts truthy defensively).
Hardening: ABORT on the live path when GITHUB_TOKEN is unset (private GHCR
images would image-pull-backoff while reporting success); warn-and-continue
only under --dry-run. Benign domain "already exists" no-op now logs the actual
matched Railway message for a forensic trail.
Harden the committed starter-fleet Railway provisioner against the
partial-failure / mistyped-flag / un-deployed-image failure modes
surfaced in CR:
- Domain idempotency: a serviceDomainCreate that Railway rejects with an
"already exists" error (start-of-run snapshot missed the domain due to
eventual consistency, or a prior run died mid-fleet) is now caught as a
benign no-op (marked "existing", logged) so a re-run converges instead
of aborting the entire remaining fleet. A genuine non-transient error
still aborts.
- Explicit redeploy: serviceCreate + serviceInstanceUpdate(source.image)
only PINS the image; it does not start a deployment, and Railway's image
auto-updates fire only on a NEW digest push. Added serviceInstanceRedeploy
after the instance update on BOTH the create and update paths so the
pinned image actually runs (and starter_smoke can find the service up).
Mirrors the documented update+redeploy pattern in bin/railway and the
explicit redeploy showcase_deploy.yml issues after each GHCR push.
- argv validation: parseArgs() now rejects any unrecognized argument
(e.g. a mistyped --dry-rn) with a usage hint before any provisioning,
instead of silently ignoring it and proceeding to REAL live provisioning.
- Fail-fast safety: validate the Railway token AND registry credentials
up front in main() (token resolution no longer process.exit()s deep in
the GraphQL boundary; main().catch owns the exit). Broadened the
withRetry transient predicate to the domain/instance eventual-consistency
class via an overridable per-call predicate. Dry-run now reports a new
service's domain as "would-create" for a faithful preview.