# UserPromptSubmit Hook Timeout RCA

**Owner for triage:** perri / claude-spt  
**Reporter:** hertz  
**Date:** 2026-07-10  
**Status:** Root pinned; immediate adapter fix available; systemic spt-core PROJECT-INDEX fix remains complementary

## Problem

The claude-spt `UserPromptSubmit` hook exceeds Claude Code's approximately 30-second hook limit on HFENDULEAM. Deployah appeared consistently affected while Doyle appeared unaffected.

The timeout prevents the hook from reliably reaching its turn-start duties:

- resolve endpoint identity;
- mark the perch busy;
- drain deferred messages;
- return additional context.

No real hook was invoked during diagnosis because doing so would mutate busy state and drain queued messages.

## Root cause

The hook asks for one endpoint ID through an enriched global listing path:

`UserPromptSubmit -> self_id -> spt whoami --json -> cmd_endpoint_list -> latest_project_ref for every local perch -> synchronous Git fanout`.

### Adapter path

- `tools/claude-spt/src/hook.rs:984-1116`: `handle_user_prompt_submit`.
- Line 987 already reads broker-injected `SPT_ENDPOINT_ID`.
- Line 1085 nevertheless calls `self_id(env, &sid)`.
- `hook.rs:567-584`: `self_id` synchronously runs `spt whoami --json`.
- `hook.rs:1653-1676`: `SysEnv::spt` waits with `child.wait_with_output()` and has no internal deadline.
- `plugin/sptc/hooks/hooks.json:6-8`: UserPromptSubmit has no timeout override; Claude Code's external hook limit terminates the chain.

### Core expansion

In spt-core 0.31.0:

- `crates/spt/src/cli.rs:6609-6612`: `cmd_whoami(json)` calls the complete `cmd_endpoint_list` renderer.
- `cli.rs:2954-2978`: JSON endpoint listing computes `latest_project_ref` for every local perch.
- `picker/data.rs:456-518`: each latest-project query reads session history, enumerates all `p-*` context branches, and checks the endpoint on every branch.
- `spt-store/src/project.rs:93-142`: recorded cwd derivation launches `git remote get-url origin` and possibly `git rev-parse --show-toplevel`.

The earlier spt-core PROJECT-INDEX RCA measured 100+ Git children and approximately 29–35 seconds for one endpoint listing on this node.

## Safe reproduction

The real hook was not executed. Its read-only identity subprocess was timed using the exact current session IDs:

| Endpoint | `spt whoami --json` |
|---|---:|
| deployah | 45.569 s |
| doyle | 45.606 s |

Both resolved the correct `self.id`; both exceeded the hook deadline before `state busy` or `poll` could run.

Claude records establish that apparent endpoint selectivity is threshold behavior:

- Deployah timeouts: 30,031 ms and 31,629 ms.
- Doyle successes: 15,967–28,022 ms.
- Doyle timeouts: 31,258 ms and 31,386 ms.

Both endpoints use the same cwd, `claude-spt:ccs` profile, `CLAUDE_CONFIG_DIR`, installed hook binary, and nearby start epochs. Concurrent Git/filesystem/cache/antivirus load determines whether the shared path crosses 30 seconds.

## Why the hook used `whoami`

The design originated as a general harness-independent identity resolver:

- spt-hosted sessions receive `SPT_ENDPOINT_ID` from the broker;
- harness-hosted/live sessions may not have that variable;
- Claude Code hook payloads provide `session_id` but not the SPT endpoint ID;
- `spt whoami` resolves from `OWL_SESSION_ID` / `SPT_AGENT_ID`, so the adapter could use one command for both hosting modes without reading core state directly.

ADR-0002 records that decision at `docs/adr/0002-hook-wiring-hand-written-hooks-json-shells-spt-api.md:93-104`.

The mistake was not choosing a session-based resolver. The mistake was binding identity resolution to a command whose implementation later became an enriched endpoint-list alias. The adapter needs only `session -> endpoint`; descriptions, remote snapshots, local project history, and Git-backed context are unrelated.

## Relationship to PROJECT-INDEX

The spt-core PROJECT-INDEX milestone will remove synchronous Git from list/picker/reporting reads and should make the current `whoami` implementation fast. It is still insufficient as the only hook fix:

1. Hook identity remains coupled to informational enrichment and index health.
2. A missing/corrupt/rebuilding index must never threaten prompt submission.
3. The adapter already holds a direct spt-hosted identity carrier.
4. Harness-hosted fallback should use a narrow core identity contract, not a list renderer.

Therefore this RCA shares the PROJECT-INDEX root but requires an adapter dependency cut now.

## Prompt-specific amplifier

For `/sptc:live` or `/live`, `hook.rs:1072-1083` also executes `spt endpoint list --show-all`, then later executes `whoami --json`, which is another endpoint list. Such prompts can pay the fanout twice.

## Exclusions

- Not caused by subnet/mesh isolation: the list delay persisted with HFENDULEAM removed from all subnets.
- Not the PTY SharedSend wedge: no poison/stall lifecycle records correlated, and local broker commands remained responsive.
- Not deployah-specific configuration: Doyle shares the same code path and has recorded timeouts.
- Not poll latency: the identity command alone exceeds the entire deadline before poll runs.
