Skip to content

SessionStart latency: serial git subprocess fan-out and redundant file re-reads #194

Description

@SUaDtL

SessionStart latency: serial git subprocess fan-out and redundant file re-reads on the linchpin hook

Two performance findings share the SessionStart hook (session-start.py), the user-facing linchpin hook whose wall-clock cost gates when the orchestrator persona reaches the model.

performance-002 (anchor): SessionStart hook serially spawns up to 8 separate git subprocesses before returning

Severity: medium | Confidence: 0.7 | Effort: M
Where:
plugins/ca/hooks/session-start.py:448-481
plugins/ca/hooks/session-start.py:304-343
plugins/ca/hooks/_githooks.py:52-68

Evidence: main() runs, in strict sequence, blocking on each: project_root() -> git rev-parse --show-toplevel; _install_git_hooks(root) -> _githooks.hooks_dir() -> git config --get core.hooksPath then (when unset) git rev-parse --git-path hooks; then assemble_summary() runs five more sequential git_read() calls (status --porcelain=v1, rev-list --left-right --count @{u}...HEAD, branch -vv, worktree list --porcelain, stash list). That is up to 8 separate subprocess.run(['git', ...]) process launches, none concurrent, all before the hook's stdout is delivered.

Impact: session-start.py runs once per session but gates when the orchestrator persona and startup state reach the model, so its wall-clock cost is directly user-facing latency at the start of every session. Each git subprocess launch carries fixed process-creation overhead, materially higher on Windows (the repo's primary dev platform per the inventory); eight sequential spawns compound that overhead instead of amortizing it.

Recommendation: Reduce spawn count where possible (skip the hooks_dir() core.hooksPath/git-path probe when the prior session already installed hooks in the same location) and/or run the independent assemble_summary() git reads concurrently (subprocess.Popen fan-out + join) instead of sequentially, since each is already documented as independent/degrade-separately.

Acceptance criteria:

  • A profiled SessionStart invocation shows either fewer git subprocess launches or the independent git reads executing concurrently rather than strictly sequentially
  • No change to the on-stdout content or any field's degrade-on-failure behavior

performance-003 (member): SessionStart re-reads CONTEXT.md/open-tasks.md/open-questions.md a second time via governance_line() on the first-of-day briefing path

Severity: low | Confidence: 0.75 | Effort: S
Where:
plugins/ca/hooks/session-start.py:465-539
plugins/ca/hooks/session-start.py:246-260
plugins/ca/hooks/session-start.py:572-577

Evidence: main() already reads CONTEXT.md (line 465, again line 497), open-questions.md (line 512), and open-tasks.md (line 525) to print the startup-state block. When mode == 'full' (first session of the day), render_full_briefing(root, summary) calls governance_line(root), which imports statusline.py and calls statusline.arbiter_state(root); that function independently re-reads CONTEXT.md, overrides.log, last-checkpoint, open-tasks.md, and open-questions.md from disk purely to recompute the same stage/tasks/q figures main() already computed moments earlier from its own reads of the same files.

Impact: On the first SessionStart of each day (the "full" briefing path), this hook re-opens and re-parses up to three files it already read in the same invocation, purely to render one extra display line. The absolute cost is small but it is pure waste on a latency-sensitive, user-facing linchpin hook that already performs multiple git subprocess spawns (performance-002); every avoidable synchronous read on this path adds to session-start wall time.

Recommendation: Either pass the already-read ctx_text/ot_text/oq_text (or the derived stage/tasks/q values) into governance_line()/arbiter_state() so it reuses main()'s reads, or compute the governance summary once and use it for both the startup-state block and the briefing line.

Acceptance criteria:

  • The full-briefing SessionStart path reads CONTEXT.md, open-tasks.md, and open-questions.md at most once each per invocation
  • governance_line's displayed stage/tasks/q/over values are unchanged

Metadata

Metadata

Assignees

No one assigned

    Labels

    sev:medTribunal/triage: medium severity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions