Skip to content

Tower serves API requests before internal wiring completes — terminal DELETE returns spurious 404 during startup window #1261

Description

@waleedkadous

Problem

Tower opens its HTTP port and accepts requests before its internal dependency wiring is complete. Requests that need _deps (set by initInstances()) during that window fail closed. Concretely: DELETE /api/terminals/:id returns 404 for a terminal that exists — while GET on the same id returns 200, because the GET path doesn't touch _deps.

Root cause

The entire boot sequence lives inside the server.listen() callback (tower-server.ts:375): stale-socket cleanup → session reconcile → orphan kill → husk sweep (#1227) → session-log retention scan (#1238/#1243) → and only then initInstances() (tower-server.ts:575). killTerminalWithShellper() (tower-instances.ts:799) starts with if (!_deps) return false, which the DELETE route (tower-routes.ts:842) translates into 404 Session not found.

The race predates 3.2.4, but the husk sweep and retention scan added in the 3.2.4 window run between port-open and deps-wiring, so the window now scales with disk state: with ~1,400 session-log files (2.2 GB) it exceeds 100ms, enough that afx tower start && <immediate afx command> can hit it. Discovered when tower-terminals.e2e.test.ts > DELETE > kills and removes a terminal began failing deterministically on a log-heavy dev machine while CI (fresh runners, no logs) stayed green. Instrumentation confirmed _deps was null at DELETE time; a 400ms delay before DELETE makes the test pass.

Suggested fix

  1. Routes that need _deps should return 503 with Retry-After ("Tower is starting up") instead of 404/false — generalizing the pattern stopInstance already uses ("Tower is still starting up. Try again shortly.").
  2. Structurally: defer server.listen() until after initInstances() (or hold requests until init completes). Note the ordering comments in the boot sequence — some steps must run before initInstances() reads architect state — so this needs care.

(1) is small and safe; (2) closes the window for good if it proves compatible with the ordering constraints.

Testing notes

The existing DELETE e2e test covers the regression once the window is closed (it currently only fails on machines with large session-log volume — CI won't catch it until the fix makes readiness deterministic).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerArea: Tower server / agent farm CLI

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions