Convert all actors from queues/workflows to direct actions, lazy task creation

Major refactor replacing all queue-based workflow communication with direct
RivetKit action calls across all actors. This works around a RivetKit bug
where c.queue.iter() deadlocks for actors created from another actor's context.

Key changes:
- All actors (organization, task, user, audit-log, github-data) converted
  from run: workflow(...) to actions-only (no run handler, no queues)
- PR sync creates virtual task entries in org local DB instead of spawning
  task actors — prevents OOM from 200+ actors created simultaneously
- Task actors created lazily on first user interaction via getOrCreate,
  self-initialize from org's getTaskIndexEntry data
- Removed requireRepoExists cross-actor call (caused 500s), replaced with
  local resolveTaskRepoId from org's taskIndex table
- Fixed getOrganizationContext to thread overrides through all sync phases
- Fixed sandbox repo path (/home/user/repo for E2B compatibility)
- Fixed buildSessionDetail to skip transcript fetch for pending sessions
- Added process crash protection (uncaughtException/unhandledRejection)
- Fixed React infinite render loop in mock-layout useEffect dependencies
- Added sandbox listProcesses error handling for expired E2B sandboxes
- Set E2B sandbox timeout to 1 hour (was 5 min default)
- Updated CLAUDE.md with lazy task creation rules, no-silent-catch policy,
  React hook dependency safety rules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathan Flurry 2026-03-16 14:17:24 -07:00
parent 29e5821fef
commit 78cd38d826
24 changed files with 887 additions and 887 deletions

View file

@ -486,7 +486,15 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
createWithInput: "app",
}) as unknown as AppOrganizationHandle;
const task = async (organizationId: string, repoId: string, taskId: string): Promise<TaskHandle> => client.task.get(taskKey(organizationId, repoId, taskId));
// getOrCreate is intentional here — this is the ONLY lazy creation point for
// virtual tasks (PR-driven entries that exist in the org's local tables but
// have no task actor yet). The task actor self-initializes from org data in
// getCurrentRecord(). Backend code must NEVER use getOrCreateTask except in
// createTaskMutation. See backend/CLAUDE.md "Lazy Task Actor Creation".
const task = async (organizationId: string, repoId: string, taskId: string): Promise<TaskHandle> =>
client.task.getOrCreate(taskKey(organizationId, repoId, taskId), {
createWithInput: { organizationId, repoId, taskId },
});
const sandboxByKey = async (organizationId: string, _providerId: SandboxProviderId, sandboxId: string): Promise<TaskSandboxHandle> => {
return (client as any).taskSandbox.get(taskSandboxKey(organizationId, sandboxId));