Skip to content

fix(webapp,run-store): make run-ops sharding failures visible and unroutable ids a 404 - #4868

Merged
d-cs merged 4 commits into
mainfrom
fix/runops-sharding-qa-followups
Sep 2, 2026
Merged

fix(webapp,run-store): make run-ops sharding failures visible and unroutable ids a 404#4868
d-cs merged 4 commits into
mainfrom
fix/runops-sharding-qa-followups

Conversation

@d-cs

@d-cs d-cs commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Four failure modes found while exercising a multi-database run-ops setup by hand. None stopped the service: each was unalertable, unreported, or answered with the wrong status.

  • A replication source whose Postgres publication carries no tables replicates nothing. Boot succeeds, the service looks healthy, and every ClickHouse-backed surface silently under-counts. There is now a counter to alert on.
  • Completing a wait token with an ID naming a database the deployment has no store for answered a server error. It now answers not-found, like the equivalent run routes.
  • An unparseable stored shard set still degrades safely to the previous ID format, but the parse failure went unreported. It is now reported on the mint path.
  • Per-shard observability, without which a gradual rollout cannot be watched.

Design

Publication misconfiguration. The replication client already detected an empty publication and emitted a LogicalReplicationClientError on every reconnect, which the service logged. That is visible but not alertable, and indistinguishable from any other client error. It now emits a PublicationMisconfiguredError subclass of that error, counted per source as runs_replication_publication_misconfigured_total. No boot-time check was added: it would need a live query per source before the service exists, so a transient blip would fail closed and refuse to boot, which is worse than the problem being solved. The counter increments on every retry, so a nonzero rate is the alarm and it clears when the publication is repaired.

Repairing the publication restores replication going forward but does not backfill rows written while it was empty, so catching this early is the point.

Unroutable IDs. All seven API route builders now map this to a 404, including the two worker builders that previously fell through to a 500. Both new call sites log before answering, so a shard key dropped from a config meant to be append-only still alarms rather than turning every live token on it into a quiet not-found.

Routes that handle their own errors never reach a builder, so a sweep covers those too: four bare API routes that answered 500 (run result, run tags, reschedule, batch results) and ten dashboard and resource routes that threw straight through to an error page. Each takes the not-found path it already had for a run that does not exist, and logs first.

Shard-set parse failures. The parsing module is pure by design and its tests depend on that, so it reports through a callback and the caller logs, matching the existing operator reports. This covers the mint path, which every process re-reads within one cache TTL, so a bad set alarms across the fleet. Saving one is still silent at save time: the stamping helper takes the same callback but its caller does not yet pass it.

Observability. runops_shard_routed_total{shard} counts ID-routed store resolutions, and runops_read_through_source_total{source,shard} records which store served a read-through. The first is deliberately a relative ramp signal rather than a request count: fan-outs and probe hits resolve no single shard, so they are not counted, and the help text says so. Label children are cached rather than hashed per call, since both sit on hot read paths.

Each fix has a test that fails if the corresponding production change is reverted, with two exceptions worth naming: the lines binding the metric recorders into the production service and read-through defaults are module-scope wiring that the tests inject around.

@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 1e352d0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

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

Click here to learn what changesets are, and how to add one.

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

@d-cs d-cs self-assigned this Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Observability map

As of 1e352d0.

20/100 over 454 measured of 472 entry points (base 20, no change)

What this PR changed
No entry point this PR touches changed its score.

FIX FIRST

  • /api/v1/projects/:projectRef/envvars (sensitive) - auth-boundary, request-context
  • /auth/sso (sensitive) - auth-boundary, request-context
  • /_app/orgs/:organizationSlug/settings/team (sensitive) - error-classification, auth-scope, request-context

AUDIT 3 of 50 sensitive mutations record an actor. 47 without one.
CONTEXT 23 of 454 entry points name a tenant on a failure path. 351 appear only here, 39 of them sensitive, in the JSON rather than the fix list.

What the score is made of
CHECKS
  error-classification  185 applicable, 106 pass,   0 sole, global without it 12
  auth-boundary          62 applicable,  57 pass,   0 sole, global without it 16
  auth-scope             19 applicable,  17 pass,   0 sole, global without it 19
  request-context       454 applicable,  23 pass, 247 sole, global without it 64
  audit-trail            50 applicable,   3 pass,   0 sole, not in the score

The score and findings here are report-only and never gate the merge. Separately, a required test suite keeps this tool's symbol and route lists in sync with the code they name, and can fail a pull request that renames or removes a symbol they reference, or that adds the first route with a segment they anticipate. Each failure names the list to edit. The rules and their reasons: internal-packages/observability-map/README.md.

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 2015dade-8944-42f3-bb84-d0d27fb62190

📥 Commits

Reviewing files that changed from the base of the PR and between befbf79 and 1e352d0.

📒 Files selected for processing (3)
  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
  • internal-packages/run-store/src/runOpsStore.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 3 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (36)
  • GitHub Check: report
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (23, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (24, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (22, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 24)
  • GitHub Check: obsmap / 🧪 Unit Tests: Observability Map
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: typecheck / typecheck
  • GitHub Check: code-quality / code-quality
  • GitHub Check: audit
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: 🛡️ E2E Auth Tests (full)
🧰 Additional context used
📓 Path-based instructions (14)
New code must target Run Engine V2 through the singleton in `app/v3/runEngine.server.ts`; do not reintroduce V1 execution paths.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
Never use `request.signal` to detect client disconnects.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
We use vitest exclusively.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
Test files must not import `app/env.server.ts`; pass configuration as options instead.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
**Prefer static imports over dynamic imports.**

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • internal-packages/run-store/src/runOpsStore.ts
  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
Add crumbs as you write code — not just when debugging.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • internal-packages/run-store/src/runOpsStore.ts
  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
Use zod for validation in packages/core and apps/webapp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
Do not import `env.server.ts` directly or indirectly into test files; instead pass environment-dependent values through options/parameters to make code testable

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
Access environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` Use subpath exports from `@trigger.dev/core` package instead of importing from the root `@trigger.dev/core` path

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
Use vitest for all tests in the Trigger.dev repository

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
Use function declarations instead of default exports

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • internal-packages/run-store/src/runOpsStore.ts
  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
Use types over interfaces for TypeScript Avoid using enums; prefer string unions or const objects instead

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • internal-packages/run-store/src/runOpsStore.ts
  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs Do not use high-cardinality attributes in OTEL metr...

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

Files:

  • internal-packages/run-store/src/runOpsStore.ts
  • apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts
  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
🧠 Learnings (1)
📚 Learning: 2026-06-16T09:19:47.637Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3960
File: apps/webapp/test/prismaInfrastructureErrorCapture.test.ts:0-0
Timestamp: 2026-06-16T09:19:47.637Z
Learning: In this repo’s Vitest setup, `vitest.config.ts` uses `globals: true`, so identifiers like `vi`, `describe`, `it`, and `expect` are available as globals in Vitest test files. During code review, do not flag missing `vi`/`describe`/`it`/`expect` imports as a runtime error or correctness issue when they’re used in `*.test.ts/tsx` or `*.spec.ts/tsx` files. Explicit imports are still preferred for consistency, but they’re not required for runtime behavior.

Applied to files:

  • internal-packages/run-store/src/runOpsStore.shardMap.test.ts
🔇 Additional comments (3)
apps/webapp/app/v3/runOpsMigration/unroutableRead.server.test.ts (1)

3-3: LGTM!

Also applies to: 16-47

internal-packages/run-store/src/runOpsStore.ts (1)

173-182: LGTM!

Also applies to: 215-222, 471-471, 489-492

internal-packages/run-store/src/runOpsStore.shardMap.test.ts (1)

352-355: LGTM!

Also applies to: 371-379, 423-431, 626-626, 742-750, 871-871, 991-995, 1013-1017


Walkthrough

The pull request adds unroutable-ID handling across API, worker, waitpoint, and run routes. It adds typed replication publication errors and per-source misconfiguration metrics. It reports invalid stored mint-shard configurations while preserving gen-1 fallback behavior. It adds read-through source metrics for shard and legacy stores. It adds per-shard routing metrics with tests that exclude misses, probes, fan-outs, fallback legs, idless operations, and initialization.

Merge Risk: 🟡 Moderate · up to 1e352

The PR improves shard-routing errors, replication diagnostics, and rollout metrics, but new diagnostic paths still lack required breadcrumbs and two tests do not follow repository-mandated integration and placement rules. Merge should wait until these readiness issues are corrected or explicitly accepted.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.90% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 42 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description gives detailed, relevant context and explains the design and testing coverage. However, it omits the required issue reference, checklist, Testing section with explicit steps, Changelog… Add the required template sections. Include a valid Closes #<issue> reference, complete the checklist, describe the commands and tests that were run, add a short changelog entry, and state N/A for screenshots if none apply.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main changes: improved run-ops sharding failure visibility and 404 responses for unroutable IDs. It is concise and specific.
Full details: Description check

Explanation

The description gives detailed, relevant context and explains the design and testing coverage. However, it omits the required issue reference, checklist, Testing section with explicit steps, Changelog section, and Screenshots section.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runops-sharding-qa-followups

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.

coderabbitai[bot]

This comment was marked as resolved.

…outable ids a 404

Four failure modes from a manual pass over a multi-database run-ops setup, three of
them silent:

- A replication source whose publication carries no tables replicates nothing while
  boot succeeds and the service looks healthy. The client now raises a typed
  PublicationMisconfiguredError and the webapp counts it per source, so it alarms.
- Completing a wait token with an id naming a database the deployment has no store
  for answered 500. Every API route builder now answers 404, matching the run routes.
- An unparseable stored shard set still degrades to the previous id format, but the
  parse failure is now reported instead of vanishing, on both the read and write paths.
- Per-shard observability, so a gradual rollout can be watched: runops_shard_routed_total
  and runops_read_through_source_total.
@d-cs
d-cs force-pushed the fix/runops-sharding-qa-followups branch from ad9e703 to ff518b5 Compare September 1, 2026 16:28
… routes

The API route builders cover the routes built on them, but routes that handle their
own errors never reach that mapping. Four bare API routes answered 500 for an id
naming a database the deployment has no store for, and ten dashboard and resource
routes threw straight through to an error page.

Each now takes the not-found path it already has for a run that does not exist: the
API routes return 404, the dashboard routes redirect or 404 as they already did. A
shared helper keeps that uniform, and every one logs first, so a shard key dropped
from a config meant to be append-only still alarms rather than reading as an absent run.
@d-cs
d-cs marked this pull request as ready for review September 2, 2026 09:05
devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

…, and count only routed traffic

Two defects from review of the previous commit.

undefinedOnUnroutableId took the read as a promise, but RoutingRunStore.findRun is
not async and resolves the shard before it returns anything, so an unroutable id threw
while the argument was still being evaluated and never reached the try block. Every
dashboard and resource route it was added to still answered a server error. It now
takes a thunk, and a test covers the synchronous-throw case that types cannot.

runops_shard_routed_total counted every call through the generic store lookup, which
probes, fan-outs and fallback legs also use, so an unrouted miss credited legacy and a
waitpoint home-miss counted twice. Counting moved to the two id-routed entry points, so
the series means what its name says. Tests cover an unrouted miss and a probe leg.
devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

An operation with no id falls back to the default store rather than resolving to one,
so counting it as id-routed traffic inflated the default series during a ramp. The
counter now fires only when an id resolved the key.

Also moves the unroutable-id helper test next to its source.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 0 new potential issues.

Devin Review

@d-cs
d-cs added this pull request to the merge queue Sep 2, 2026
Merged via the queue into main with commit 17f29b7 Sep 2, 2026
64 checks passed
@d-cs
d-cs deleted the fix/runops-sharding-qa-followups branch September 2, 2026 13:05
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.

2 participants