Build Log
Alloy — Runner Bridge and First Real Claude Execution
July 19, 2026
Proved Alloy can initiate real Claude Code execution via a separate local runner process (verified twice via CLI/SDK capability spikes, real session IDs, real permission boundary), built the full runner/command/event pipeline end to end and typechecked clean, then pivoted before a live UI click to a screen-by-screen completion approach after the Charge/Ignite mockup gap surfaced live.
Timeline
What shipped
products/alloy/lib/db/schema.ts — execution_commands table (durable, idempotent runner commands), arc_events gained source/sourceSequence/rawProviderPayload/occurredAt columns, executions gained cwd, arc_event_type gained 8 runner/provider event types
products/alloy/drizzle/migrations/0001_giant_lizard.sql — generated and pushed to the real Alloy Neon database (first real schema deploy for this app)
products/alloy/lib/runner-protocol.ts — ExecutionProvider interface, ProviderCapabilities, NormalizedProviderEvent — provider-neutral by construction
products/alloy/runner/providers/claude-cli.ts — the CLI adapter, using a verified PreToolUse-hook permission boundary (not CLI flags, which were tested and found unreliable)
products/alloy/runner/index.ts — the standalone runner process: registers with Alloy, polls for commands, runs them through the provider, posts real events back
products/alloy/runner/package.json + tsconfig.json — a separate, independently-runnable Node package, deliberately outside the root npm workspaces (matches the topology decision that Alloy and the runner never assume they're on the same machine)
products/alloy/lib/runner-auth.ts — shared-bearer-token auth for the runner<->Alloy boundary (not Clerk — the runner isn't a browser session)
products/alloy/app/api/runner/{register,claim,events,complete}/route.ts — the 4 authenticated API routes implementing the protocol
products/alloy/proxy.ts — fixed to exempt /api/runner/* from Clerk's auth.protect() (found live: without this, the runner could never reach Alloy at all)
products/alloy/lib/actions.ts — startExecution() (the real Ignite action), setPhase() (fixes the forward-only phase-transition gap found via direct grep), fixed createWorkpiece()'s redirect to a route that never existed
products/alloy/components/IgnitePanel.tsx + app/ignite/page.tsx — the real, minimal Ignite screen (button + execution status), intentionally not matching the full IgniteLayout.png mockup — see nextItems