Skip to content

WEB-4507: Capture Claude Code skill & agent invocations from hooks#99

Open
anonpran wants to merge 3 commits into
mainfrom
WEB-4507-skill-agent-capture
Open

WEB-4507: Capture Claude Code skill & agent invocations from hooks#99
anonpran wants to merge 3 commits into
mainfrom
WEB-4507-skill-agent-capture

Conversation

@anonpran

@anonpran anonpran commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

What & why

Phase 1 of WEB-4507 (Skills application): detect Claude Code skill and agent/subagent invocations during agentic operations and send them to the backend. Analytics surfacing and policy enforcement are separate later phases.

Changes (claude-code/hooks/unbound.py)

  • parse_transcript_file: capture Skill + Task/Agent tool_use blocks from the session transcript, with tool_use_id, cwd, gitBranch. PostToolUse does not reliably fire for these, so the transcript is the source of truth.
  • collect_subagent_skill_tool_uses (new): sweep <session>/subagents/*.jsonl — subagents write to separate transcripts the parent Stop never reads — so skills/agents invoked inside a subagent are captured too (flagged is_subagent).
  • build_llm_exchange: forward transcript tool_uses, skip the duplicate PostToolUse copies (they lack a reliable id), and tag each with trigger (namespace-aware: user-typed /skill vs agent auto-invoke) and is_subagent. tool_use_id enables downstream dedup.

Testing

Verified e2e against real session transcripts: Skill + Agent capture, subagent sweep (a genuinely-missed subagent skill now captured), and namespace-aware trigger (/stripe:test-cardsuser; /builder vs build no false-positive).

Notes

  • Pairs with ai-gateway-data PR (backend converter skill/agent branches + idempotent dedup).
  • Targets main (this repo has no staging).

🤖 Generated with Claude Code

Greptile Summary

This PR adds Phase 1 of skill/agent invocation capture: parse_transcript_file now records Skill, Task, and Agent tool_use blocks from the parent session transcript, and a new collect_subagent_skill_tool_uses helper sweeps subagent sidecar transcripts that the parent Stop event never sees. build_llm_exchange skips the id-less PostToolUse duplicates for these tool types and instead enriches each transcript-sourced entry with trigger (user vs agent) and is_subagent metadata before forwarding to the backend.

  • parse_transcript_file — adds an elif branch inside the assistant content loop that collects Skill/Task/Agent tool_use items with tool_use_id, cwd, and git_branch; entries without an id are skipped so every row landing in the backend carries a dedup key.
  • collect_subagent_skill_tool_uses — new function that globs {session_dir}/subagents/*.jsonl, applies the same timestamp filter and id guard, and flags all hits is_subagent: True; results are merged into transcript_tool_uses in process_stop_event.
  • build_llm_exchange — filters out PostToolUse events for the three tool names, then appends the richer transcript-sourced entries, resolving trigger via a three-way namespace-aware comparison (full match, bare vs namespaced, or namespaced vs bare).

Confidence Score: 5/5

Safe to merge — the change is additive (new collection path + enriched metadata), guarded throughout with try/except, and both previous blocking concerns (null tool_use_id and namespace false-positive) are addressed in the current code.

All three new code paths (parent transcript capture, subagent sweep, trigger tagging) have appropriate guards: the content_item.get('id') requirement ensures no entry reaches the backend without a dedup key, os.path.isdir silently skips sessions with no subagent directory, and individual subagent file failures are caught and continued. The trigger detection uses three exact comparisons that correctly distinguish namespaced from bare skill names without the original suffix-only false positive.

No files require special attention; the single changed file is well-contained and the logic is straightforward.

Important Files Changed

Filename Overview
claude-code/hooks/unbound.py Adds transcript-based capture of Skill/Task/Agent tool invocations and a subagent sweep; id guard and namespace-aware trigger detection are in place, no blocking issues found.

Sequence Diagram

sequenceDiagram
    participant CC as Claude Code (Stop event)
    participant PSE as process_stop_event
    participant PTF as parse_transcript_file
    participant CSA as collect_subagent_skill_tool_uses
    participant BLE as build_llm_exchange
    participant API as Unbound API

    CC->>PSE: Stop event (transcript_path, session_id)
    PSE->>PTF: parse parent transcript (user_prompt_timestamp)
    PTF-->>PSE: "transcript_data {tool_uses, messages, usage, model}"
    PSE->>CSA: sweep subagents dir (transcript_path, user_prompt_timestamp)
    CSA-->>PSE: "subagent tool_uses (is_subagent=True)"
    PSE->>PSE: merge → transcript_tool_uses
    PSE->>BLE: build_llm_exchange(events, transcript_tool_uses, ...)
    BLE->>BLE: skip PostToolUse Skill/Task/Agent (no stable id)
    BLE->>BLE: tag each transcript tool_use with trigger + is_subagent
    BLE-->>PSE: exchange dict
    PSE->>API: POST /v1/hooks/claude (exchange)
Loading

Reviews (3): Last reviewed commit: "WEB-4507: trim verbose comments (no func..." | Re-trigger Greptile

Detect skill and agent/subagent invocations during agentic operations and
send them to the backend (analytics later consume this).

- parse_transcript_file: capture Skill + Task/Agent tool_use blocks from the
  session transcript (tool_use_id, cwd, gitBranch). PostToolUse is unreliable
  for these, so the transcript is the source of truth.
- collect_subagent_skill_tool_uses: sweep <session>/subagents/*.jsonl so
  skills/agents invoked inside subagents (separate transcripts the parent Stop
  never reads) are captured too, flagged is_subagent.
- build_llm_exchange: forward transcript tool_uses, skip the duplicate
  PostToolUse copies, and tag trigger (namespace-aware user-typed /skill vs
  agent auto-invoke) + is_subagent. tool_use_id enables downstream dedup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@anonpran anonpran requested a review from a team June 3, 2026 13:09
Comment thread claude-code/hooks/unbound.py Outdated
Comment thread claude-code/hooks/unbound.py
anonpran and others added 2 commits June 3, 2026 18:58
- build_llm_exchange trigger: replace pure-suffix match with full / bare-vs-
  namespaced (either direction) exact comparisons, so two skills sharing a base
  name across namespaces (ns1:deploy vs ns2:deploy) no longer mislabel an
  agent invocation as user-typed.
- parse_transcript_file + collect_subagent_skill_tool_uses: only capture
  tool_use blocks that carry an id, so a row can always dedup on tool_use_id
  (avoids None ids that the partial unique index would skip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Condensed the multi-line explanatory comments added for skill/agent capture
to concise one-liners. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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