{
  "summary": "Root cause is a local, synchronous Git-process fanout in project projection, not mesh dialing and not the historical PTY `SharedSend` wedge. Controlled field evidence: `spt endpoint list --json` completed in 30.065 s (rc=0), and the runtime cross-check remained ~29.6 s after HFENDULEAM was removed from every subnet / with `subnets=[]`; `spt api endpoint-info hertz` completed in 1.210 s; `spt daemon status` completed in 0.335 s. Current disk evidence is 13 flat local perches under `%LOCALAPPDATA%\\spt-core\\owlery` and 7 `p-*` branches under `tracked/.seed.git/refs/heads`. For each local endpoint, `endpoint list --json` calls `latest_project_ref` -> `project_refs_for` -> `project_history_for`; that executes one `git for-each-ref` plus one `git show` for every `p-*` branch. Therefore the present list performs at least `13 × (1 + 7) = 104` sequential Git children before cwd-derived project lookups. The 1.210 s `endpoint-info hertz` control is especially discriminating: hertz has no `cwd`, so its project projection performs exactly the same single-endpoint `1 + 7 = 8` branch Git children, whereas the list repeats that work for all 13 perches. Bare/partial `endpoint run` forms route through the picker, whose startup calls the same `gather_endpoints` projection before terminal setup, so they inherit the fanout. Fully qualified `--adapter A --id E` and lone `--id E` when E has a recorded adapter route directly to `cmd_endpoint_run` and bypass `gather_endpoints`; their distinct possible waits are local broker/session/attach waits, not the list root. No 15 s or 30 s timeout is on the endpoint-list call graph: 30.065 s is cumulative ordinary child-process overhead, not `2 × 15 s`. A single Git plumbing call is bounded at 30 s, cwd Git probes at 10 s, but if either timeout were actually firing per endpoint, the 13-endpoint total would be far above 30 s. Strongest safe fix: batch the context-store projection once per CLI snapshot and memoize cwd derivation across endpoints. Use one `branches_by_recency` call, then `BranchStore::ls_files_at` once per `p-*` branch to derive all endpoint memberships, reducing branch Git work from `N × (1+B)` to `1+B`; cache `project_id_and_display_for_dir` by normalized cwd across all endpoints. Preserve ordering (session cwd newest-first, then origin cwd, then context branches by recency) and existing degrade-to-empty behavior.",
  "files": [
    {
      "path": "spt-core/crates/spt/src/cli.rs:1284-1378",
      "description": "Endpoint command dispatch. `EndpointCmd::List` enters `cmd_endpoint_list`; `EndpointCmd::Run` calls `resolve_run_target`. `RunTarget::Direct` calls `cmd_endpoint_run`; `RunTarget::Picker` calls `picker::run`."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:1679-1813",
      "description": "Exact endpoint-run router and its quadrant test. `(Some adapter, Some id)` is Direct. `(None, Some id)` is Direct only when `info.adapter` exists; otherwise Picker. `(Some adapter, None)` and `(None, None)` are Picker. `resolve_run_target_quadrants` pins all forms."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:2739-2993",
      "description": "JSON endpoint-list pipeline. Lines 2857-2983 load snapshots from disk, call `roster::detect_self_id`, enumerate all local perches, and map every local perch through `picker::data::latest_project_ref` at 2961-2968. There is no broker request, adapter query, peer probe, or network dial."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:2994-3088",
      "description": "Human endpoint-list pipeline. It similarly enumerates every local perch and calls `latest_project_ref` at 3054-3059. Remote rows are projected from already-persisted registry snapshots only."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:4122-4343",
      "description": "Fast daemon-status control. It queries the local broker for net status, stall-evict count, and image, then enumerates local perches. Lines 4207-4209 explicitly leave `project=None` to avoid the per-perch Git cost, explaining why daemon status is fast despite touching broker and roster."
    },
    {
      "path": "spt-core/crates/spt/src/picker/mod.rs:40-72",
      "description": "Picker startup order. Before terminal setup it calls `project_name_and_display`, `adapter_options`, `gather_endpoints`, and `home_subnet_options`. Thus every run form routed to Picker pays the endpoint project-history projection before a TUI appears."
    },
    {
      "path": "spt-core/crates/spt/src/picker/data.rs:23-75",
      "description": "Picker's cwd-project and adapter collection. `project_name_and_display` performs cwd Git derivation. Adapter options are local registry file reads (`registry::registered`, shipped profiles, local profile directory); they do not contact an adapter process or daemon."
    },
    {
      "path": "spt-core/crates/spt/src/picker/data.rs:103-125",
      "description": "`gather_endpoints` calls `local_rows` and `subnet_rows`; the subnet data is loaded from local snapshot files. This is the shared slow spine reached by picker-based endpoint-run forms."
    },
    {
      "path": "spt-core/crates/spt/src/picker/data.rs:209-285",
      "description": "`local_rows` enumerates each perch and calls `project_refs_for` for every endpoint. Offline rows additionally compute resume rows, another cwd-project derivation path."
    },
    {
      "path": "spt-core/crates/spt/src/picker/data.rs:404-529",
      "description": "Primary root. `project_refs_for` reads the session ledger and calls `project_history_for(id)` before deriving cwd projects. `project_history_for` opens the same Git store, calls `branches_by_recency`, loops every branch, and calls `read_at_tip` for each `p-*` branch. `latest_project_ref` still builds the complete history and only then takes `.next()`, so endpoint list pays full history cost merely to render one project column."
    },
    {
      "path": "spt-core/crates/spt/src/picker/data.rs:534-590",
      "description": "Existing good local memoization precedent: `resume_rows_from` memoizes project derivation per unique cwd, but only within one endpoint. The proposed list/picker fix should lift that cache to the whole gathered snapshot."
    },
    {
      "path": "spt-core/crates/spt/src/picker/data.rs:1098-1135",
      "description": "Existing pure project-history truth tests pin newest-first session ordering, owlery exclusion, fresh-origin fallback, and store-branch union. They avoid Git via an injected derivation closure, so they do not catch process-count scaling."
    },
    {
      "path": "spt-core/crates/spt-store/src/branchstore.rs:204-318",
      "description": "Git operations behind the fanout. `branches_by_recency` is one `for-each-ref`; each `read_at_tip` calls `read_at`, which spawns `git show`. `ls_files_at` at 304-327 already provides the safe bulk seam: one `git ls-tree -r --name-only` per project branch can derive membership for every endpoint at once."
    },
    {
      "path": "spt-core/crates/spt-store/src/gitrun.rs:19-140",
      "description": "Every Git call spawns a child, drains stdout/stderr on threads, polls every 10 ms, and has `GIT_TIMEOUT=30 s`. These calls execute synchronously at their call sites. The bound prevents infinite hangs but does not prevent `N×B` normal-process overhead."
    },
    {
      "path": "spt-core/crates/spt-store/src/project.rs:19-143",
      "description": "Cwd project derivation. `PROJECT_GIT_TIMEOUT=10 s`; it tries `git remote get-url origin`, then `git rev-parse --show-toplevel` if needed. Repeated equal cwds across endpoints are not globally memoized."
    },
    {
      "path": "spt-core/crates/spt/src/api/reporting.rs:915-980",
      "description": "`api endpoint-info`. An explicit id bypasses self detection, reads one local perch, then calls `latest_project_ref` once at 953-954. This makes the 1.210 s hertz timing a one-endpoint control for the list's repeated projection."
    },
    {
      "path": "C:/Users/decid/AppData/Local/spt-core/owlery/hertz/info.json",
      "description": "Field evidence: hertz has no `cwd`, so its project projection performs no cwd Git probes; its observed 1.210 s is attributable to the context-branch projection (one branch enumeration plus seven project-branch membership reads)."
    },
    {
      "path": "C:/Users/decid/AppData/Local/spt-core/owlery",
      "description": "Field layout contains 13 flat readable `info.json` perches: doyle, perri, deployah, emphasys, todlando, mobile-gw, hertz, lia, flynn, scratch-c, scratch-d, chert, hall-bf."
    },
    {
      "path": "C:/Users/decid/AppData/Local/spt-core/tracked/.seed.git/refs/heads",
      "description": "Field layout contains 14 loose heads, of which exactly seven are `p-*`: bigscreen spt-bs-core, bigscreen omp-spt, SaberMage spt-core, liaison, claude-spt, spt-mobile, and projects. Non-`p-*` agent branches are enumerated but do not trigger `read_at_tip` in `project_history_for`."
    },
    {
      "path": "spt-core/crates/spt-runtime/src/registry.rs:448-510",
      "description": "Adapter registry enumeration is synchronous local file I/O: list record directories, read manifests, parse TOML. No adapter subprocess/query or network request is involved."
    },
    {
      "path": "spt-core/crates/spt-runtime/src/registry.rs:568-580,769-835",
      "description": "Local profiles are directory reads; direct run adapter resolution loads one record/manifest and optional overlay. These are not candidates for a 15/30 s delay."
    },
    {
      "path": "spt-core/crates/spt/src/roster.rs:38-86,149-213",
      "description": "Roster enumeration is local info/status/address reading. Self detection falls back to a bounded-depth native process ancestry walk. It can contribute small OS-query cost once, but cannot explain the 13×8 Git-correlated scaling."
    },
    {
      "path": "spt-core/crates/spt-store/src/proc.rs:298-418",
      "description": "Windows ancestry uses `OpenProcess` + `NtQueryInformationProcess`, maximum depth 40, with no tasklist/WMIC subprocess. This falsifies a PowerShell/WMIC 30 s explanation."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:1811-2435",
      "description": "Direct endpoint-run core. It resolves a local manifest, ensures the daemon, probes the local broker session table, spawns locally, and optionally waits for/attaches to the PTY. It never calls `gather_endpoints` or project-history projection."
    },
    {
      "path": "spt-core/crates/spt/src/rc.rs:858-903",
      "description": "Direct-run session probe: `SessionProbe::connect` uses pump-mode local broker IPC with a 10 s per-call deadline; `has_session` requests the broker session table. A delayed fully-qualified run can spend this 10 s, but this path cannot affect endpoint list."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:2398-2435",
      "description": "Post-spawn attach gate: `SESSION_AWAIT_SECS=25`, polling every 150 ms. This only applies after a successful direct/picker-dispatched spawn when not `--start`; it is not list or pre-picker latency."
    },
    {
      "path": "spt-core/crates/spt/src/rc.rs:1396-1413,1635-1639,1768-1776",
      "description": "Terminal attach phase bounds: 10 s broker IPC setup deadline, 30 s reconnect window with 1 s pauses, and 30 s first-event grace. These identify attach-specific delays and must be measured separately from picker data gathering and spawn."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/harnesshost.rs:233-304",
      "description": "Direct spawn path. Preparation is local manifest/template work; then `Brain::cold_start` and `spawn_session_pid` request the local broker. It does not dial mesh peers."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/brain.rs:317-360,443-480",
      "description": "Ordinary `Brain::cold_start` is an unbounded blocking local IPC carrier; `spawn_session_pid` waits for `Spawned`. In contrast, `cold_start_pump` supports an explicit per-call deadline. This is a separate hardening opportunity for direct-run spawn, not the established list/picker root."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/broker.rs:3101-3175",
      "description": "Broker `KIND_SESSIONS` handling snapshots local sessions, opportunistically reaps already-stalled controllers, converges stamps, and replies. It performs no mesh work and does not wait 15 s for a controller to become stale."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/broker.rs:153-188",
      "description": "Current physical broker connection constants: controller backpressure 5 s and brain/local-connection physical write deadline 15 s (`BRAIN_WRITE_DEADLINE`, test-overridable)."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/conn.rs:412-610",
      "description": "v0.30.6 `BrokerConn` replacement for `SharedSend`: every physical connection write is serialized under an absolute bound, then poisoned/canceled/severed if stuck. This closes the old indefinite named-pipe write wedge."
    },
    {
      "path": "spt-core/crates/spt-daemon/tests/conn_blackhole_lifecycle.rs:1-63",
      "description": "Standing Windows regression for the historical PTY freeze. It reproduces a non-draining controller and proves unrelated sessions continue, the physical connection is canceled, handlers exit, fresh attach works, and `KIND_SESSIONS` remains prompt."
    },
    {
      "path": "spt-core/CHANGELOG.md:68-89",
      "description": "v0.30.6 history explicitly records the bounded/cancelable session-write fix. Current broker image is 0.31.0 per the supplied field context, so pre-fix behavior is not the primary current explanation."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:7424-7529,7645-7768",
      "description": "Mesh-sensitive subnet-status path. Only `subnet status --nodes` probes peers. `PROBE_TIMEOUT=2500 ms`, `MAX_INFLIGHT=16`, with wall time `ceil(k/16)×2.5 s`; ordinary subnet status is disk-only. This is the correct recheck discriminator."
    },
    {
      "path": "spt-core/crates/spt/src/wansend.rs:594-611",
      "description": "Each stale-node liveness probe ensures daemon, opens local broker IPC, obtains an address, dials, then requests subnet serving status. This network path is absent from endpoint list and picker gathering."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/nethost.rs:64-72,960-1003",
      "description": "Broker QUIC operations have a 10 s bound. Mesh dial failures can cost this on remote operations, but `subnet status --nodes` externally caps them at 2.5 s and endpoint list never invokes them."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/pump/mod.rs:800-881,939-958",
      "description": "Address-bootstrap deletion trap: a dial-failed presence event calls `PeerAddrStore::drop_seed`; the next round falls back to id-only discovery. This affects background peer convergence/remote dials, not local disk projection."
    },
    {
      "path": "spt-core/crates/spt-store/src/peeraddrs.rs:1-125",
      "description": "Peer address cache contract and deletion implementation. The cache is only an optimization before id-only discovery; dropping an exact route can strand the current isolated mesh in repeated discovery failures, but there is no call edge from endpoint list/project projection to this store."
    },
    {
      "path": "spt-core/crates/spt/tests/list_json_liveness_parity_e2e.rs:1-220",
      "description": "Existing endpoint-list JSON E2E claims the command is a pure disk-read surface and uses an isolated home. Its fixture has no tracked Git store/cwd project history, so it does not exercise or detect the `N×B` subprocess fanout; its 30 s outer deadline is too loose and coincidentally equal to the field latency."
    },
    {
      "path": "spt-core/crates/spt/src/cli.rs:12626-12710",
      "description": "Current project-column unit tests validate render and JSON shape only. They pass prebuilt project refs and do not assert collection complexity or Git invocation count."
    },
    {
      "path": "spt-core/traceable-reqs.toml:548-550,1661-1663,1677-1679",
      "description": "History of the regression: REQ-PICKER-2 introduced per-endpoint Git branch enumeration; REQ-PICKER-PROJECT-HISTORY-TRUTH expanded the full union; REQ-ENDPOINT-LIST-PROJECT-COL then reused `latest_project_ref = full project_refs_for head`, bringing the expensive picker loader onto every list row."
    }
  ],
  "architecture": "End-to-end call graph and phase separation:\n\n1. `spt endpoint list --json`\n`cli dispatch (1294-1305)` -> `cmd_endpoint_list(..., json=true) (2827+)` -> disk `SubnetStore::load` -> disk `load_snapshots` -> optional one self-detection -> `roster::enumerate` -> for each local perch `latest_project_ref` -> `project_refs_for` -> `sessions::read_all` + `project_history_for(id)` -> `BranchStore::branches_by_recency` (one Git child) -> for every `p-*` branch `read_at_tip` (one Git child) -> for session/origin cwds `project_id_and_display_for_dir` (one successful `git remote`, or remote failure plus `git rev-parse`) -> JSON serialization. Local broker request time = 0; registry/network fanout time = 0; adapter discovery time = 0; terminal attach time = 0. Persisted WAN snapshots are ordinary file reads only.\n\n2. `spt endpoint run` mode distinction\n- `endpoint run` bare: `(adapter=None,id=None)` -> Picker -> `picker::run` -> `gather_endpoints` -> same full local project projection. Slow root applies.\n- `endpoint run --adapter A` only: `(Some,None)` -> Picker with adapter prefilled -> same gather. Slow root applies.\n- `endpoint run --id E` where E is missing or `info.adapter` absent: `(None,Some)` -> Picker with id prefilled -> same gather. Slow root applies.\n- `endpoint run --adapter A --id E` (regardless of `--create/--resume`, `--start/--attach/--view`, `--subnet`, `--save`): `(Some,Some)` -> Direct -> bypasses gather/project projection.\n- `endpoint run --id E` where E has a recorded `info.adapter`: Direct with that adapter -> bypasses gather/project projection.\n- Picker action after the user selects a target eventually dispatches to the same `cmd_endpoint_run`; therefore time before TUI paint is project fanout, while later spawn/attach time belongs to the direct phases below.\n\n3. Direct local run phases\n`cmd_endpoint_run` -> local adapter `resolve_option` -> `ensure_running` -> `SessionProbe::cold_start_pump(10 s)` -> `KIND_SESSIONS` -> live decision. If offline: skeleton disk write -> `launch_harness_brokered_in` -> ordinary local `Brain::cold_start` -> `KIND_SPAWN` -> wait for `SPAWNED`. `--start` returns immediately after spawn. Attach/view continues through a 25 s session-existence polling budget, then the rc pump, whose setup IPC is 10 s and first-event grace is 30 s. These are falsifiable phase markers: time `--start` vs default attach; if `--start` is fast but default is slow, the delay is attach; if the TUI itself appears late, it is gather/project projection. The ordinary spawn carrier is presently unbounded, while physical broker writes are bounded at 15 s.\n\n4. Mesh/network path\nOnly remote operations and `subnet status --nodes` dial peers. `--nodes` batches up to 16 probes under a 2.5 s outer ceiling. Background pump dials use 10 s broker QUIC bounds; failed exact cached routes are deleted and the next attempt becomes id-only discovery. Removing HFENDULEAM from all subnets without changing the ~30 s list latency conclusively agrees with the static call graph: the list never dials.\n\n5. PTY/SharedSend path\nThe old freeze was an output subscriber that stopped draining a Windows named pipe. v0.30.6 replaced the unbounded shared send half with a per-connection watchdog/abort/poison mechanism; the relevant production bound is 15 s. It can affect broker replies or terminal attach/spawn if the local control plane is unhealthy, but it cannot cause `endpoint list --json` because that command opens no broker connection. A fast daemon-status broker round trip (0.335 s) and current broker 0.31.0 further lower this hypothesis. Attach-only 15/30 s behavior remains separately falsifiable with fully qualified `--start` versus default attach.\n\nTimeout arithmetic:\n- Established list root: present field minimum is 104 sequential branch Git children = `N(1+B)` with `N=13`, `B=7`; plus at least one cwd Git child for endpoints carrying an eligible cwd, and sometimes two when the first command fails. Observed 30.065 s is their sum. Individual bounds are 30 s for branch Git and 10 s for each cwd Git command, but no bound expired in the observed 30 s aggregate; repeated expiration would multiply far beyond 30 s.\n- Endpoint-info hertz: `1+B = 8` branch Git children, no cwd children, observed 1.210 s.\n- Mesh status `--nodes`: `ceil(k/16) × 2.5 s` outer maximum.\n- Direct run pre-spawn session query: one 10 s per-call IPC deadline.\n- Direct run attach gate: nominal 25 s polling plus individual queries; successful normal queries are immediate.\n- rc setup: 10 s; rc first-event/reconnect: 30 s.\n- physical broker write: 15 s; controller queue stall: 5 s.\n\nRanked falsifiable hypotheses:\n1. HIGH / established: per-endpoint full Git history projection. Prediction: latency remains with no subnets/daemon; scales with flat perch count and project-branch count; a single explicit endpoint-info costs approximately one endpoint slice. All observed facts match.\n2. MEDIUM for fully qualified direct runs only, zero for list: local broker session/spawn IPC. Prediction: direct `--adapter A --id E --start` delays near 10/15 s while bare picker delay may differ; instrument boundaries around `SessionProbe` and `spawn_session_pid`. It cannot explain local-only list.\n3. MEDIUM for attach-mode tail only, zero for list/pre-TUI: rc first-event/session attach. Prediction: the same fully qualified run becomes fast with `--start`, while default attach delays 25/30 s. SharedSend black-hole tests cover the old physical wedge.\n4. LOW: ancestry/liveness enumeration. Prediction: commands that self-detect but omit project projection would be similarly slow; implementation is native bounded-depth syscalls, not a subprocess. Field timings and 104-child arithmetic disfavor it.\n5. REJECTED for target delay: mesh isolation / peer-cache deletion. Prediction would be latency disappearing when subnets are removed; it did not. It remains the root of remote reachability failures and can affect `subnet status --nodes`, remote rc/send/wake, and the background pump.\n6. REJECTED for list; attach-only possibility already fixed/hardened: historical PTY `SharedSend` freeze. No list call edge exists, current broker is post-v0.30.6, and the physical retirement regression exists.\n\nSafe fix/test shape:\n- Add a batch project catalog in `picker/data.rs`, consumed once by both `cmd_endpoint_list` and `gather_endpoints`. Open `BranchStore` once, call `branches_by_recency` once, and for each `p-*` branch call existing `ls_files_at` once. Parse paths shaped `<endpoint>/<PROJECT_CONTEXT_FILE>` into `endpoint -> recency-ordered project ids`. Complexity becomes `1+B`, independent of endpoint count.\n- Add a gather-scope cache keyed by normalized cwd so `project_id_and_display_for_dir` executes once per unique cwd across all endpoints; retain the existing within-resume-row memoization.\n- Keep `project_refs_from` as the pure ordering/union kernel, supplying precomputed store ids and a cached derive closure. Preserve session-newest-first, owlery exclusion, origin fallback, branch-recency order, display/disambiguation, and error-to-empty behavior.\n- Focused regression seam: inject a counting catalog loader and cwd resolver into a pure `project_refs_for_many`/snapshot builder. With 13 endpoints and 7 project branches, assert catalog enumeration occurs once, branch tree listing occurs exactly 7 times, and equal cwds derive once—not 13×8. Assert returned heads match the current per-endpoint semantics.\n- Behavioral E2E: isolated `SPT_HOME` with many perches and multiple `p-*` branches, invoke real `endpoint list --json`, validate every project field and an invocation counter from the injected/store seam. Avoid a fragile wall-clock-only test; optionally retain a generous watchdog as a no-hang backstop. Extend the picker gather test to prove it uses the same batch catalog. Keep `resolve_run_target_quadrants` unchanged and add a test that only Picker targets invoke the gather closure, while both Direct forms do not."
}