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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions