Skip to content

/health never responds while indexing runs: brood prepare and registration resync execute synchronously on the event loop #22

Description

@thenotoriousllama

Field report (nectar 0.1.3, Windows, honeycomb 0.2.3 fleet, ~85k-file repo)

/health on 3854 never responds even after indexing completes. Memory ramps to ~868MB walking the tree, plateaus and declines to ~670MB (walk finished), the port is a clean Listen, earlier /health connections went CloseWait then cleared, and a fresh GET /health still times out indefinitely. Fleet-status therefore omits nectar and the Hive dashboard stalls on "starting" forever.

Root-cause investigation (current main, 2026-07-04)

The daemon is one Node process; GET /health is served by node:http on the same event loop that runs the indexing pipeline, and the pipeline contains multi-minute fully synchronous stretches:

  1. Discovery blocks: spawnGitLsFiles uses spawnSync (src/brooding/discovery.ts:117-124), and the walk fallback is a synchronous readdirSync generator (src/registration/disk-fs.ts:100).
  2. Content prep blocks hardest: prepareFiles is a tight synchronous loop over every discovered file (src/brooding/precheck.ts:126-133), and each readContent is a bare readFileSync (src/registration/disk-fs.ts:70) followed by hashing. On ~85k files this is minutes of uninterrupted synchronous work.
  3. The registration cold-catch-up resync is one macrotask: runCycle (src/registration/service.ts:290-320) enumerates fs.listPaths() (sync walk) and runs processOne (statSync + readFileSync + hash + TLSH) for every path inside a while loop with no awaits and no yields. The function is async in name only on this path.

While any of these run, the HTTP handler cannot be scheduled: the socket accepts (kernel backlog) but the request never gets serviced, which is exactly the reported Listen-but-timeout behavior.

Mitigation already shipped (PR #21)

Dormant-by-default (PRD-019) means a fresh boot no longer indexes anything, so /health answers immediately at boot and the Hive gate clears. But the moment a project is activated and a brood or cold resync starts on a large repo, the starvation above recurs.

Ask

Make /health respond independently of indexing, structurally:

  • Move discovery + prepare + resync work off the event loop (worker thread), or make the loops yield (async iteration with per-N-files awaits and async fs), or both.
  • Bound the per-macrotask work so no single stretch exceeds a small budget.
  • Add a regression test: /health must answer within a bounded time WHILE a large synthetic brood/resync is in flight.

Related: the post-index busy-loop is filed separately (it also starves /health after indexing).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions