Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/architecture/01-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ Module-load phase (runs at import time, before `startRouter()`):
| `webhook-processor.ts` | Generic 12-step pipeline (see [02-webhook-pipeline](./02-webhook-pipeline.md)) |
| `platform-adapter.ts` | `RouterPlatformAdapter` interface |
| `adapters/` | Per-provider adapter implementations |
| `worker-manager.ts` | Spawns/monitors Docker worker containers |
| `worker-manager.ts` | BullMQ worker processor orchestration, capacity-slot waiting, and dispatch retry classification |
| `container-manager.ts` | Compatibility facade that assembles worker metadata/env and coordinates Docker worker spawn fallback |
| `worker-container-launcher.ts` | Docker worker create/start/wait wiring, active-worker registration, and router timeout timer setup |
| `worker-spawn-settings.ts` | Docker-free worker image, snapshot-reuse, timeout, and container-name resolution |
| `worker-exit-handler.ts` / `worker-timeouts.ts` | Post-exit cleanup/diagnostics and router-side timeout cancellation internals |
| `queue.ts` | BullMQ `addJob()`, queue stats |
| `action-dedup.ts` | In-memory deduplication of webhook deliveries |
| `work-item-lock.ts` | Prevents concurrent agents on the same work item |
Expand Down
9 changes: 9 additions & 0 deletions docs/architecture/10-resilience.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,15 @@ Periodic scan for Docker containers that outlived their expected lifetime (watch

When a worker container exits non-zero, the router inspects it before Docker AutoRemove can reap it and writes a grep-stable error reason: `Worker crashed with exit code N · OOMKilled=<true|false> · reason="<State.Error>"`. `OOMKilled=true` is the definitive cgroup OOM signal; exit 137 without that marker means something else sent the signal.

## Worker Lifecycle Internals

`src/router/worker-manager.ts` owns the BullMQ processor lifecycle: capacity-slot waiting, retry classification, and handoff to worker spawning. Docker worker lifecycle details sit behind `src/router/container-manager.ts`, which remains the compatibility facade for callers while delegating focused work to helpers:

- `worker-spawn-settings.ts` resolves the effective image, snapshot reuse, timeout, and Docker-safe container name.
- `worker-container-launcher.ts` creates/starts the Docker container, registers active-worker state, sets the router timeout timer, and monitors `wait()`.
- `worker-exit-handler.ts` owns post-exit inspection, log tailing, snapshot commit/removal, and cleanup ordering.
- `worker-timeouts.ts` owns router-side timeout cancellation, timed-out run marking, and timeout notifications.

## Snapshot Management

`src/router/snapshot-manager.ts`, `src/router/snapshot-cleanup.ts`
Expand Down
17 changes: 17 additions & 0 deletions src/router/adapters/linear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,23 @@ export class LinearRouterAdapter implements RouterPlatformAdapter {

async resolveProject(event: ParsedWebhookEvent): Promise<RouterProjectConfig | null> {
const config = await loadProjectConfig();
// parseWebhook already selected the right cascade project (by team +
// Linear Project scope when multiple cascade projects share a Linear
// team) and embedded its id on the LinearParsedEvent extension. Use
// that id directly — re-looking up by teamId would re-introduce the
// `.find()` first-match shadow that PR #1332 fixed in parseWebhook.
// Closes a prod regression on 2026-05-11 where MNG-638 (ucho's
// Linear Project) routed to `cascade` because both cascade projects
// share the same Linear team and `cascade` appeared first in the
// projects array.
const linearEvent = event as LinearParsedEvent;
if (linearEvent.projectId) {
return config.projects.find((p) => p.id === linearEvent.projectId) ?? null;
}
// Fallback: legacy call sites that construct a bare `ParsedWebhookEvent`
// without the Linear extension's projectId still get teamId-based
// lookup. Single-cascade-project-per-team setups continue working
// unchanged.
return config.projects.find((p) => p.linear?.teamId === event.projectIdentifier) ?? null;
}

Expand Down
Loading
Loading