Skip to content

perf: faster session startup and statusline rendering#40

Closed
barkain wants to merge 3 commits into
mainfrom
fix/statusline-slow-startup
Closed

perf: faster session startup and statusline rendering#40
barkain wants to merge 3 commits into
mainfrom
fix/statusline-slow-startup

Conversation

@barkain

@barkain barkain commented Apr 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Consolidate 3 SessionStart hooks into 1 script (saves ~0.7s per startup)
  • Use stdin JSON for version and context instead of subprocess/file scanning (saves ~1s per render)

Changes

SessionStart hooks consolidated

  • Combined inject_workflow_orchestrator.py, inject-output-style.py, inject_token_efficiency.py into single inject_all.py
  • Reads stdin once, outputs merged additionalContext
  • Updated plugin-hooks.json to reference single script

Statusline performance

  • Version: reads from stdin version field instead of spawning claude --version (920ms → 0ms)
  • Context: reads from stdin context_window instead of parsing JSONL files
  • Fallback: cached claude --version with 1-hour TTL if stdin unavailable

Impact

  • Session startup: ~1.7s faster
  • Statusline render: ~1s faster per refresh

Test plan

  • Fresh claude start with plugin loads within reasonable time
  • Statusline displays version, context, cost correctly
  • SessionStart injects orchestrator stub, output style, token efficiency

🤖 Generated with Claude Code

barkain added 3 commits April 6, 2026 16:55
Reduces session startup overhead from ~1.1s (3x Python/uv startup) to
~0.4s (1x) by combining inject_workflow_orchestrator, inject-output-style,
and inject_token_efficiency into a single inject_all.py that reads stdin
once and merges all additionalContext into one JSON response.
@qodo-code-review

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Consolidate SessionStart hooks and optimize statusline rendering with stdin JSON

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Consolidate 3 SessionStart hooks into single inject_all.py script (~0.7s faster startup)
• Read version/context from stdin JSON instead of subprocess calls (~1.9s faster statusline renders)
• Implement 1-hour TTL cache for claude --version fallback when stdin unavailable
• Update plugin version to v1.17.0 and plugin-hooks.json configuration
Diagram
flowchart LR
  A["3 separate hooks<br/>inject_workflow_orchestrator.py<br/>inject-output-style.py<br/>inject_token_efficiency.py"] -->|consolidate| B["Single inject_all.py<br/>reads stdin once<br/>merges additionalContext"]
  C["Subprocess calls<br/>claude --version<br/>JSONL file scans"] -->|optimize| D["stdin JSON fields<br/>version<br/>context_window"]
  D -->|fallback| E["Cached version<br/>1-hour TTL<br/>tempfile"]
  B -->|result| F["~1.7s faster<br/>session startup"]
  D -->|result| G["~1.9s faster<br/>statusline render"]
Loading

Grey Divider

File Changes

1. hooks/SessionStart/inject_all.py ✨ Enhancement +145/-0

New consolidated SessionStart hook script

hooks/SessionStart/inject_all.py


2. hooks/SessionStart/inject_workflow_orchestrator.py Miscellaneous +0/-126

Removed in favor of consolidated inject_all.py

hooks/SessionStart/inject_workflow_orchestrator.py


3. hooks/SessionStart/inject-output-style.py Miscellaneous +0/-52

Removed in favor of consolidated inject_all.py

hooks/SessionStart/inject-output-style.py


View more (6)
4. hooks/SessionStart/inject_token_efficiency.py Miscellaneous +0/-68

Removed in favor of consolidated inject_all.py

hooks/SessionStart/inject_token_efficiency.py


5. scripts/statusline.py ✨ Enhancement +59/-10

Optimize version and context retrieval with stdin JSON

scripts/statusline.py


6. hooks/plugin-hooks.json ⚙️ Configuration changes +1/-11

Update SessionStart hooks to use single inject_all.py

hooks/plugin-hooks.json


7. .claude-plugin/plugin.json ⚙️ Configuration changes +1/-1

Bump version to 1.17.0

.claude-plugin/plugin.json


8. .claude-plugin/marketplace.json ⚙️ Configuration changes +1/-1

Bump version to 1.17.0

.claude-plugin/marketplace.json


9. CHANGELOG.md 📝 Documentation +8/-0

Document performance improvements in v1.17.0

CHANGELOG.md


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Tests call removed hook 🐞 Bug ≡ Correctness
Description
The PR switches SessionStart to run only inject_all.py, but tests still import/execute
hooks/SessionStart/inject_token_efficiency.py, which no longer exists, causing immediate CI failures
(ImportError/FileNotFoundError).
Code

hooks/plugin-hooks.json[R84-90]

        "hooks": [
          {
            "type": "command",
-            "command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject_workflow_orchestrator.py\"",
+            "command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject_all.py\"",
            "timeout": 20
-          },
-          {
-            "type": "command",
-            "command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject-output-style.py\"",
-            "timeout": 10
-          },
-          {
-            "type": "command",
-            "command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject_token_efficiency.py\"",
-            "timeout": 10
          }
        ]
Evidence
SessionStart is now configured to execute inject_all.py, while tests still reference the old
inject_token_efficiency.py path and expect its behavior, so the test suite will fail on missing file
and/or wrong outputs.

hooks/plugin-hooks.json[81-90]
tests/conftest.py[50-56]
tests/test_integration.py[93-125]
hooks/SessionStart/inject_all.py[1-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SessionStart hooks are consolidated to `inject_all.py`, but the test suite still imports/runs `hooks/SessionStart/inject_token_efficiency.py` and asserts its old output semantics. This will break CI because the file is no longer present and the consolidated behavior differs.

## Issue Context
- SessionStart hook config now points to `inject_all.py`.
- Tests/fixtures still hardcode `inject_token_efficiency.py`.

## Fix Focus Areas
Choose one of:
1) **Update tests to target `inject_all.py`**
  - Adjust fixtures and integration/unit tests to load/run `inject_all.py`.
  - Update assertions: `CLAUDE_TOKEN_EFFICIENCY=0` should disable only the token-efficiency section, not necessarily produce empty stdout.

2) **Add backward-compatible wrapper scripts**
  - Re-introduce `inject_token_efficiency.py` (and optionally the other two scripts) as thin wrappers that call into `inject_all.py` or preserve old behavior for tests/users.

### Files to edit
- hooks/plugin-hooks.json[81-90]
- tests/conftest.py[50-56]
- tests/test_integration.py[93-125]
- hooks/SessionStart/inject_all.py[1-15]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Version cache type crash 🐞 Bug ☼ Reliability
Description
get_claude_version_cached() assumes the cache JSON is a dict with a numeric ts; if the cache file is
malformed-but-parseable (e.g., ts is null/string or JSON is not a dict), the subtraction/data.get
path can raise TypeError/AttributeError and break statusline rendering.
Code

scripts/statusline.py[R314-322]

+    cache_file = Path(tempfile.gettempdir()) / "claude_version_cache.json"
+    try:
+        if cache_file.exists():
+            data = json.loads(cache_file.read_text(encoding="utf-8"))
+            ts = data.get("ts", 0)
+            if datetime.now().timestamp() - ts < 3600:
+                return data.get("version", "v?")
+    except (OSError, json.JSONDecodeError, ValueError):
+        pass
Evidence
The cache read path calls data.get() and subtracts ts from a float timestamp, but only catches
OSError/JSONDecodeError/ValueError; TypeError/AttributeError from unexpected types are not handled.

scripts/statusline.py[314-322]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`get_claude_version_cached()` can raise `TypeError` or `AttributeError` when the cache file contains valid JSON with unexpected types (e.g., `{"ts": null}` or a non-dict JSON value). This would crash the statusline.

## Issue Context
The cache file is stored in the OS temp dir and can become corrupted/partially written or manually edited.

## Fix Focus Areas
- Validate `data` is a `dict` before calling `.get()`.
- Coerce/validate `ts` as a number (int/float) before subtracting.
- Expand exception handling to include `TypeError`/`AttributeError`.

### Fix Focus Areas (code pointers)
- scripts/statusline.py[314-322]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. stdin version type crash 🐞 Bug ☼ Reliability
Description
main() assumes stdin JSON "version" is a string and calls .startswith(); if "version" is provided as
a non-string (e.g., int/float), statusline rendering will raise AttributeError instead of falling
back to cached subprocess version.
Code

scripts/statusline.py[R484-494]

+    # Get Claude version — prefer stdin JSON, fall back to cached subprocess
+    stdin_version = (
+        input_data.get("version", "") if isinstance(input_data, dict) else ""
+    )
+    if stdin_version:
+        # Normalize: add "v" prefix if missing
+        claude_version = (
+            stdin_version if stdin_version.startswith("v") else f"v{stdin_version}"
+        )
+    else:
+        claude_version = get_claude_version_cached()
Evidence
The code branches on truthiness of stdin_version and then calls a string method without validating
its type.

scripts/statusline.py[484-494]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Statusline version normalization calls `stdin_version.startswith("v")` without ensuring `stdin_version` is a string, which can crash if the JSON producer emits a numeric version.

## Issue Context
The code intends to prefer stdin JSON and fall back to `get_claude_version_cached()`.

## Fix Focus Areas
- Guard with `isinstance(raw, str)` and `raw.strip()` before using `.startswith()`.
- Otherwise, fall back to `get_claude_version_cached()`.
- Optionally: `stdin_version = str(raw)` but only if you’re sure numeric values are safe to stringify.

### Fix Focus Areas (code pointers)
- scripts/statusline.py[484-494]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread hooks/plugin-hooks.json
Comment on lines 84 to 90
"hooks": [
{
"type": "command",
"command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject_workflow_orchestrator.py\"",
"command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject_all.py\"",
"timeout": 20
},
{
"type": "command",
"command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject-output-style.py\"",
"timeout": 10
},
{
"type": "command",
"command": "uv run --no-project --script \"${CLAUDE_PLUGIN_ROOT}/hooks/SessionStart/inject_token_efficiency.py\"",
"timeout": 10
}
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Tests call removed hook 🐞 Bug ≡ Correctness

The PR switches SessionStart to run only inject_all.py, but tests still import/execute
hooks/SessionStart/inject_token_efficiency.py, which no longer exists, causing immediate CI failures
(ImportError/FileNotFoundError).
Agent Prompt
## Issue description
SessionStart hooks are consolidated to `inject_all.py`, but the test suite still imports/runs `hooks/SessionStart/inject_token_efficiency.py` and asserts its old output semantics. This will break CI because the file is no longer present and the consolidated behavior differs.

## Issue Context
- SessionStart hook config now points to `inject_all.py`.
- Tests/fixtures still hardcode `inject_token_efficiency.py`.

## Fix Focus Areas
Choose one of:
1) **Update tests to target `inject_all.py`**
   - Adjust fixtures and integration/unit tests to load/run `inject_all.py`.
   - Update assertions: `CLAUDE_TOKEN_EFFICIENCY=0` should disable only the token-efficiency section, not necessarily produce empty stdout.

2) **Add backward-compatible wrapper scripts**
   - Re-introduce `inject_token_efficiency.py` (and optionally the other two scripts) as thin wrappers that call into `inject_all.py` or preserve old behavior for tests/users.

### Files to edit
- hooks/plugin-hooks.json[81-90]
- tests/conftest.py[50-56]
- tests/test_integration.py[93-125]
- hooks/SessionStart/inject_all.py[1-15]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@barkain

barkain commented Apr 8, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #41, which absorbed this branch's commits (1856ccc consolidate SessionStart hooks, db47525 stdin version/context, c93998f trim injection) along with the soft-enforcement work. All shipped in v2.0.0.

@barkain barkain closed this Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant