Context
Caught while running scripts/dev-memgraph.sh test-graph-model against a real Claude Code session (map #288/PR #292).
The chain:
claude_code.py's adapter propagates a tool call's own tool_input into that event's metadata (if "tool_input" in payload: metadata["tool_input"] = payload.get("tool_input")).
- When the top-level session uses the Task tool to delegate to a subagent, the prompt text itself is that tool call's
tool_input — so if the prompt happens to literally mention a real skill's path (e.g. "read the file skills/release/SKILL.md and summarize it"), that string ends up in the parent tool call's own metadata, not just the subagent's.
SkillGraphConnector._on_tool_end calls _extract_skill_file_read(tool_input=None, metadata=event.metadata), which scans metadata for any string that looks like a resolvable SKILL.md path — with no way to distinguish "a path the caller is delegating" from "a path this tool call actually read."
- Since the hook subprocess's cwd typically resolves that relative path to a real file,
_metadata_from_skill_file reads it for real and records a skill-file-read — at the top level (no agent_name on this event, since it's the parent's own PostToolUse), even though nothing actually read the file at that level; the actual read happened inside the subagent, correctly recorded separately there.
Net effect: a session-level (:Session)-[:USED_SKILL]->(:Skill) edge gets created for a skill nobody read at the session level — just mentioned in a prompt that got delegated onward. This directly contradicts skills-graph's own documented distinction (its CONTEXT.md: "reading arbitrary Markdown or merely mentioning a skill name does not" create Skill Usage).
Worked around for now in test-graph-model's own prompt (making the subagent locate the file itself rather than handing it a literal path), not fixed at the source.
Scope
- Decide whether
_extract_skill_file_read's metadata scan should exclude tool_input/prompt-shaped fields specifically for tool calls whose own tool_name is an agent-spawning one (i.e. don't treat "what the parent asked a subagent to do" as "what the parent itself read").
- Or: only trust an actual
Read-tool (or equivalent) tool call/result for file-path detection, not an arbitrary tool's tool_input/metadata string scan.
Context
Caught while running
scripts/dev-memgraph.sh test-graph-modelagainst a real Claude Code session (map #288/PR #292).The chain:
claude_code.py's adapter propagates a tool call's owntool_inputinto that event'smetadata(if "tool_input" in payload: metadata["tool_input"] = payload.get("tool_input")).tool_input— so if the prompt happens to literally mention a real skill's path (e.g. "read the file skills/release/SKILL.md and summarize it"), that string ends up in the parent tool call's own metadata, not just the subagent's.SkillGraphConnector._on_tool_endcalls_extract_skill_file_read(tool_input=None, metadata=event.metadata), which scansmetadatafor any string that looks like a resolvableSKILL.mdpath — with no way to distinguish "a path the caller is delegating" from "a path this tool call actually read."_metadata_from_skill_filereads it for real and records a skill-file-read — at the top level (noagent_nameon this event, since it's the parent's ownPostToolUse), even though nothing actually read the file at that level; the actual read happened inside the subagent, correctly recorded separately there.Net effect: a session-level
(:Session)-[:USED_SKILL]->(:Skill)edge gets created for a skill nobody read at the session level — just mentioned in a prompt that got delegated onward. This directly contradicts skills-graph's own documented distinction (itsCONTEXT.md: "reading arbitrary Markdown or merely mentioning a skill name does not" create Skill Usage).Worked around for now in
test-graph-model's own prompt (making the subagent locate the file itself rather than handing it a literal path), not fixed at the source.Scope
_extract_skill_file_read's metadata scan should excludetool_input/prompt-shaped fields specifically for tool calls whose owntool_nameis an agent-spawning one (i.e. don't treat "what the parent asked a subagent to do" as "what the parent itself read").Read-tool (or equivalent) tool call/result for file-path detection, not an arbitrary tool'stool_input/metadata string scan.