Repository navigation
Skill should use CLI subcommands instead of inline python -c #197
Description
Problem
The Claude Code skill (skill.md) passes all pipeline steps as inline Python via python -c "...". This triggers multiple security heuristics in Claude Code:
- Command substitution —
$(cat graphify-out/.graphify_python)to resolve the interpreter triggers "Contains command_substitution" prompts - Comments in arguments — multi-line Python with
#comments triggers "Newline followed by # inside a quoted argument can hide arguments from path validation"
These prompts cannot be auto-accepted via Claude Code's permission allow-rules, so users must click "Yes" on every single step (~30 times per pipeline run). This makes the skill nearly unusable without manually patching the skill file.
Root cause
The skill uses Bash as a code delivery mechanism. Every step is:
$(cat graphify-out/.graphify_python) -c "
import json
from graphify.detect import detect
# ... 10-20 lines of Python
"This is the wrong abstraction layer. The Python code should live behind CLI entry points, not inside shell strings.
What already works
graphify query was added as a CLI subcommand in v0.3.8 (#43), and graphify hook, graphify save-result, graphify claude also exist as CLI commands. But the skill doesn't use graphify query — it still constructs inline Python for queries.
Proposed fix
Expose the remaining pipeline steps as CLI subcommands:
graphify detect <path> # Step 2
graphify extract <path> # Step 3 (AST + semantic dispatch)
graphify build # Step 4
graphify cluster # Step 4 (or --cluster-only)
graphify label # Step 5
graphify export --html --obsidian --svg # Step 6-7
graphify benchmark # Step 8
Then update skill.md to call these instead of inline Python. Benefits:
- No security prompts —
graphify detect .is a normal command, no heuristics triggered - Scoped permissions — users can allow
Bash(graphify *)which is constrained to what the CLI exposes, not arbitrary Python execution - No injection risk — arguments are CLI args parsed by argparse, not interpolated into Python strings
- Simpler skill — the skill becomes a sequence of CLI calls instead of a Python code generator
Workarounds users are currently doing
- Patching
skill.mdlocally to replace$(cat graphify-out/.graphify_python)with the literal path (avoids v3: semantic query with embeddings #1 but not v3: Codex and OpenClaw support #2) - Adding broad allow-rules in settings.json (doesn't bypass content-based heuristics)
- Clicking "Yes" 30 times per run
Environment
- graphifyy 0.4.1
- Claude Code (CLI)
- Ubuntu 24.04
Activity
biggates commentedon Apr 16, 2026
Previous issue #114 does not solve all of them.
nothariharan commentedon Oct 10, 2026
Fixed in #4275 — and here's exactly what was wrong, how it's fixed, and what's deliberately left out.
Why #114 didn't cover this
#114 replaced one block — the inline
save_query_resultPython — withgraphify save-result(v0.3.18), then closed. Every other pipeline step and every on-demand reference flow kept its inline Python. biggates' follow-up comment in #114 flagged exactly that ("there are still a lot of other raw python scripts in skills… are you going to do all of them?"), but it was never turned into work — which is what this issue tracks.Root cause
Each step ran as:
Two Claude Code heuristics fire on that, once per step, and neither is allowlistable:
command_substitution(the$(cat …)) and "newline followed by#in a quoted argument" (the Python comments). ~30 manual approvals per run.How it's fixed
Every step is now a plain console command. The step bodies moved into a new
graphify/pipeline.pyand are exposed asgraphify pipeline <step>—detect,extract-ast,empty-semantic,cache-check,merge-chunks,save-cache,merge-semantic,merge-extraction,build,diagnose,label,save-manifest,cleanup, plusvocab,transcribe,detect-incremental,code-only-check,empty-extract,update-merge,graph-difffor the reference flows. The references now call those plus existing commands (graphify query/path/explain/save-result/add/watch) and thegraphify-mcpconsole script.Result: no
$(cat graphify-out/.graphify_python), no inlinepython -c, andBash(graphify *)is now allowlistable — so the ~30 prompts per run are gone and the injection surface shrinks (arguments go through argparse instead of string interpolation).The skill is generated from
tools/skillgen/fragments/; the edits are in the fragments and all generated artifacts were regenerated + blessed, so nothing drifts.Verified
.graphify_*sidecars,graph.json,manifest.json, andGRAPH_REPORT.mdon a real corpus — same graph, different delivery.--updateflow and the#479shrink guard) and a drift guard that fails if any split host or reference reintroduces an inline-cblock.What's intentionally not in this PR
skill-aider.mdandskill-devin.mdstill contain inline Python (37 and 39 blocks). That's not Claude Code — those are the Aider and Devin hosts, which #197 doesn't report. They're also a genuinely wide change: unlike the split hosts, they aren't generated fromcore.md+ references — each is a hand-maintained ~1,300–1,400-line monolith that inlines everything, and they're frozen against a pinned v8 blob bymonolith_roundtrip, which requires every changed line to match a sanctioned-diff predicate. Converting them means rewriting 76 blocks and adding a new sanctioned-diff predicate and re-validating the round-trip. That's a clean follow-up PR on its own; folding it in would have ballooned the blast radius of this one.So: #197 (Claude Code) is fully resolved; the aider/devin monoliths are the one tracked follow-up. Step 1's one-time interpreter detection still uses
$(…)— that's inherent to installing/detecting graphify, and it's a single block, not per-step.test(skill): point persisted-scan-options guard at the pipeline steps (…
feat(skill): run the pipeline steps as `graphify pipeline` subcommands (
fix(skill): harden pipeline subcommands after review (Graphify-Labs#197
feat(skill): convert the on-demand reference flows to CLI calls (Gra…
fix(skill): address reference-conversion review findings (Graphify-L…
test(skill): point persisted-scan-options guard at the pipeline steps (…
fix(skill): carry the v8 fixes into the pipeline steps after rebase (G…
feat(skill): run the pipeline steps as `graphify pipeline` subcommands (
fix(skill): harden pipeline subcommands after review (Graphify-Labs#197
feat(skill): convert the on-demand reference flows to CLI calls (Gra…
fix(skill): address reference-conversion review findings (Graphify-L…
test(skill): point persisted-scan-options guard at the pipeline steps (…
fix(skill): carry the v8 fixes into the pipeline steps after rebase (G…
14 remaining items
chore(skill): regenerate artifacts after rebase onto 0.9.92 (Graphify…
test(query): parallel relations survive the CLI traversal on 0.9.92 (G…
test(skill): drive the code-only update and backtick guards through t…
feat(skill): run the pipeline steps as `graphify pipeline` subcommands (
fix(skill): harden pipeline subcommands after review (Graphify-Labs#197
feat(skill): convert the on-demand reference flows to CLI calls (Gra…
fix(skill): address reference-conversion review findings (Graphify-L…
test(skill): point persisted-scan-options guard at the pipeline steps (…
fix(skill): carry the v8 fixes into the pipeline steps after rebase (G…
test(query): retarget the fallback-edge guard at the CLI traversal (G…
chore(skill): regenerate artifacts after rebase onto 0.9.92 (Graphify…
test(query): parallel relations survive the CLI traversal on 0.9.92 (G…
test(skill): drive the code-only update and backtick guards through t…
feat(pipeline): convert the Step B3.5 coverage check into a pipeline …
chore(skill): regenerate artifacts after rebase onto 0.9.93 (Graphify…