readRecentBootstrapTranscriptOutcome ran extractBootstrapFailureReason (regex +
optional JSON.parse + ~40 substring scans per call) for every candidate line, once
per team member: an anonymous transcript line (no agentName) passes the per-member
filter for ALL members, so an N-member team re-extracted the same line's failure
reason N times per scan -- a top main-thread cost in the warm launch profile (~5.4%
with isBootstrapInstructionPrompt).
Memoize the result on the shared parsed-tail line (parsedBootstrapTranscriptTailCache
already reuses the same line objects across members and re-scans while the file is
unchanged). The compute stays LAZY inside the newest-first candidate loop, so each
line is extracted at most once across all members AND lines past the first match are
never extracted -- preserving the early-break that an eager precompute would lose.
The memo is keyed implicitly by the line's cache entry (filePath + mtime + size); a
file change re-parses into fresh line objects, so it cannot drift from the line text.
Pure-function memoization: byte-identical input (candidate.text === parsedLine.text)
-> identical output, so failure/success/null outcomes and reasons are unchanged.
Bootstrap-transcript outcome tests pass (306), no behavior change.
Checkpoint of in-progress work:
- renderer: team messages panel/composer, messagesPanelLogic, teamSlice,
AnimatedHeightReveal plus their tests
- main: runtime process usage-stats caching (ignoreCachedMisses, bounded
eviction), alive-run-id helpers, team watch-scope notify wiring
Note: the getTeamAgentRuntimeSnapshot rssBytes expectation in
TeamAgentLaunchMatrix.safe-e2e is environment-dependent and still red.
readRecentBootstrapTranscriptOutcome opened the session file and stat'd it
through the handle BEFORE checking the per-(file,mtime,size) outcome cache, so
every cache hit still paid a full open()+close(). During a tracked launch the
per-member lookup cache is bypassed, so the project-dir scan re-runs every poll
across every recent session file x every member, turning each cache hit into a
wasted open() syscall. The native sample of a 6-member mixed launch put __open
at ~51-54k; correcting the earlier attribution, this open-before-cache-check
(NOT the watcher rebuilds addressed in the previous commit) is the dominant
source -- removing the per-rebuild watcher churn left __open essentially flat.
Stat the path with fs.promises.stat (no fd) for the cache check and return the
cached outcome without opening. getParsedBootstrapTranscriptTail now opens the
file itself, lazily, only when its shared parse cache also misses (the file
genuinely changed since last parse), so a hit on either the per-member outcome
cache or the shared tail-parse cache avoids the open entirely. The tail read is
wrapped in try/finally to close the handle. Parsing/scan logic is byte-for-byte
unchanged; only the redundant open is removed. Bootstrap-transcript tests pass.
readBootstrapTranscriptOutcomesInProjectRoot iterated every .jsonl in the project
dir, opening + tail-reading each per member per bootstrap poll. A real project dir
(e.g. ~280 session files) made this the dominant file-open churn during launch (the
native sample showed ~56k open() syscalls).
A transcript last modified before the lookup window cannot contain a bootstrap line
at/after sinceMs (append-only logs: a line's timestamp <= its write time <= the file
mtime), so readRecentBootstrapTranscriptOutcome returns null for it. Skip those with
a cheap stat instead of opening them; a 5s slack absorbs clock skew between the line
timestamp source and the filesystem mtime. Behavior is unchanged (only files that
would have returned null are skipped); bootstrap-transcript detection tests still pass.
isBootstrapTranscriptContextText and getBootstrapTranscriptSuccessSource each ran
text.replace(/\s+/g,' ').trim().toLowerCase() internally. During launch the
bootstrap scan checks every transcript line against every context member for every
member's poll, so the same line was re-normalized up to (members x contextMembers)
times per cycle. Profiling a 6-member mixed launch showed isBootstrapTranscriptContextText
at ~11% main-thread JS even after the shared-parse cache.
Precompute the normalized form once per parsed line (already cached) and pass it to
both detection helpers via a new optional precomputedNormalizedText parameter. The
value is identical to what the helpers computed internally, so detection is byte-for-byte
unchanged; the helpers stay backward compatible for callers that omit it.
During launch the live-status loop resumes every alive member every audit cycle.
resumeActiveIntervalsForMember runs a synchronous file-lock + full read of every
task file, so for an N-member team with M task files it did N locked passes x M
readFileSync per cycle (e.g. 6 members x 20 task files), blocking the main event
loop. Profiling a 6-member mixed launch showed mutateTeamTasks/withFileLockSync as
a top main-thread cost (~14%).
Add resumeActiveIntervalsForMembers that applies the identical per-member resume
logic against a member set in a single locked pass, and use it in the live-status
loop. Same mutations, but one lock + task read per cycle instead of one per member.
Adds a test covering multi-member resume in one pass.
During launch, the bootstrap-wait loop polls each member and, per member, re-read
and re-JSON.parsed the same growing transcript tail (readRecentBootstrapTranscriptOutcome
was the top main-thread JS hotspot at ~21% during bootstrap, ~40% with its helpers).
The same file was parsed once per member per poll.
Memoize the parsed tail by (filePath, mtime, size) in a shared cache so the file is
read + parsed once per change and reused across all members. The per-member filter
and failure/success scan is byte-for-byte the same logic; only the redundant read +
JSON.parse is removed. Cache is bounded (LRU, same cap as the outcome cache) and
invalidated on mtime/size change, matching the existing outcome cache semantics.
Adds a test asserting the tail is parsed once and shared while per-member outcome
detection is unchanged.
The main process watched every team directory under ~/.claude/teams (one shallow
chokidar target per team root, per team inboxes, and per task dir). On macOS this
falls back to kqueue, which needs one fd per watched file, so a workspace with
many teams kept ~1600 descriptors open and made startup and reconcile work scale
with the number of teams on disk.
Scope the team-root and task watching to teams that are running or currently
engaged in the UI. The teams root and every team's inboxes are still watched for
all teams, so cross-team message delivery, the lead inbox->stdin relay, and
notifications are unchanged. Idle teams are static, so dropping their team-root/
task watches is safe; opening a team (getData) or launching it re-adds it via an
immediate watch-scope refresh. The provider falls back to watching every team
when unset, and the EMFILE polling fallback is intentionally left unscoped so a
scope change can never look like a deletion.
Measured on a 162-team workspace: open team fds 1600 -> 730, with team-root
watching restored the moment a team is opened or goes live.
- Carry bootstrap run ids from bootstrap-state into member evidence and compare them with current run identity.
- Allow small confirmation clock skew for delayed Anthropic app acceptance without accepting stale rapid relaunch evidence.
- Clean confirmed bootstrap members that only have stale persisted runtime pid diagnostics.
- Cover process-table unavailable, post-stop stale pid and mixed launch reconcile cases.