Skip to content

fix(skill): drop backticks bash ran inside Part C and the update merge (#4461) - #4462

Closed
Deepjyoti-Sarmah wants to merge 2 commits into
Graphify-Labs:v8from
Deepjyoti-Sarmah:fix/skill-backtick-substitution
Closed

Deepjyoti-Sarmah wants to merge 2 commits into
Graphify-Labs:v8from
Deepjyoti-Sarmah:fix/skill-backtick-substitution

Conversation

@Deepjyoti-Sarmah

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #4461.

Four comment lines inside double-quoted -c "..." blocks had backticks. Bash expands backticks inside double quotes, so it ran the text before Python started. This PR drops the backticks, keeps the words, and adds a test that fails on any backtick inside such a block.

Summary

 # Part C, in core/core.md:317, core/aider.md:378, core/devin.md:442 (15 generated skills)
-# `graphify update` deleted it along with the re-extracted code file it
+# graphify update deleted it along with the re-extracted code file it

 # --update merge block, in references/shared/update.md:110 (all 14 skills/<host>/references/update.md)
-# Do NOT add `changed` here: with root= passed, prune_set relativizes to the same base
+# Do NOT add changed here: with root= passed, prune_set relativizes to the same base

The skills run Python as $(cat graphify-out/.graphify_python) -c "...". With graphify on PATH, bash ran a full graphify update in the middle of Part C and pasted its output into the Python source. The first output line landed in the comment. The next lines were pasted into the code, and Part C failed with SyntaxError. .graphify_extract.json was never written. Without graphify on PATH the only sign was command not found on stderr, so nobody noticed.

Two commits:

  1. test(skill): no command runs from backticks in -c "..." blocks: tests/test_skill_no_backtick_substitution.py. On its own it fails 58 cases (29 per check).
  2. fix(skill): drop backticks bash ran inside Part C and the update merge (#4461): the 4 fragment lines, the regenerated files, and one gen.py line.

Evidence

  • Test before and after: unfixed v8 58 failed, 105 passed. After the fix 163 passed. The 58 are 29 files per check: 15 monoliths plus 14 references/update.md.

  • Real graphify on PATH (Part C copied verbatim from graphify/skill.md:377-417, a project with a previous build and one edited file, venv graphify first on PATH):

    before (v8 fe085ced)
      exit 1   SyntaxError: invalid syntax. Perhaps you forgot a comma?
               at "[graphify watch] 3 file(s) not classified ..." (graphify's own stdout)
      graph.json rewritten by the unrequested update (sha prefix e4df2084c90f7f07 -> 70d4be72e1eab745)
      .graphify_extract.json: not written
    
    after (this branch)
      exit 0   stdout: Merged: 1 nodes, 0 edges (1 AST + 0 semantic)   stderr: empty
      graph.json unchanged (sha prefix fa4465c9b74a2269 before and after)
      .graphify_extract.json: written (237 bytes)
    
  • Other cases, v8: a fake graphify printing 3 lines gives exit 1 and SyntaxError; one printing 1 line works, because the line lands inside the comment; no graphify on PATH gives exit 0 with command not found. A fake changed printing 3 lines in the --update merge block gives exit 1 and SyntaxError, with graph.json and manifest.json untouched.

  • The # Save scan root so \graphify update` ...lines in Step 1,shell/posix.mdandshell/powershell.mdare plain shell comments outside quotes. Checked: a fakegraphify` was not called.

Merge Danger

Door: two-way. Comment text only, in four fragment lines. No stored format changes, and reverting is one commit.

Blast Radius: narrow. Comments inside Python source the skills hand to the interpreter. Nothing in graphify/*.py changes.

Not covered

  • The runtime check runs only $(cat graphify-out/.graphify_python) -c "..." statements in fenced bash blocks (quoted or not). Other shapes, such as "$PYTHON" -c "...", are not run; the static check still scans them.
  • $VAR expansion inside the double quotes is not a command, so neither check looks at it.
  • The static check looks for backticks only. It does not look for $(...) inside the quotes; the runtime check does, through the shell trace, for the statements it runs.
  • The windows monolith's Part C is a PowerShell here-string, so the runtime check does not run it. The static check skips @'...'@ here-strings on purpose. The runtime test skips on win32.
  • The test reads the generated files. It does not render them again.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Tests or CI
  • Refactor
  • Security fix

Verification & Invariants

Invariant: no command runs while bash expands a skill's double-quoted -c "..." block, so the Python source Part C and the update merge hand to the interpreter is exactly what the skill text says.

  • tests/test_skill_no_backtick_substitution.py checks it two ways over graphify/skill*.md and graphify/skills/** (128 files). Static: no backtick inside any double-quoted -c block, with PowerShell @'...'@ here-strings skipped. Runtime: each -c "..." statement of the 29 files that hold Part C or the --update merge is run under bash with a stub interpreter, with a fake graphify and a fake changed first on PATH. The shell is traced, and PATH holds only the fakes, cat and the stub, so a backtick naming any other command is a trace line the test rejects.

  • It has scanner unit tests and two runtime controls, so it cannot pass with nothing in it: a backticked graphify update is caught through the fake, and a backticked unknown command is caught through the trace. A guard test fails if the Part C and update files are not found.

  • Generated or persisted state this invalidates: none. --bless rewrites 30 expected/ files (the other 104 are content-identical).

  • Read the CONTRIBUTING.md guide.

  • Reproduced the issue and identified the invariant.

  • Made the smallest fix necessary.

  • Added a regression test (if bug fix) or isolated boundary test.

  • Kept the PR description synchronized with the final implementation.

  • Documented any limitations / unsupported cases explicitly.

How was this tested?

Run from /var/tmp with PYTHONPATH set to the worktree and the repo venv's Python, so the code under test is this branch. graphify.__file__ was checked to be inside the worktree.

python -m pytest tests/test_skill_no_backtick_substitution.py -q --basetemp=... -p no:cacheprovider
  unfixed v8 (test file only):  58 failed, 105 passed
  both commits:                 163 passed

python -m tools.skillgen --bless              -> blessed 134 artifact(s) into expected/.
python -m tools.skillgen
python -m tools.skillgen --check              -> check OK: 134 artifact(s) match committed output and expected/.
python -m tools.skillgen --monolith-roundtrip -> monolith-roundtrip OK: each monolith matches v8 modulo the enum unification.

python -m pytest tests/test_skillgen.py tests/test_skillgen_input_path_injection.py -q
  96 passed
python -m pytest <the 65 .py test files that mention "skill"> tests/test_skill_no_backtick_substitution.py -q
  2363 passed, 3 skipped

python -m pytest -q --basetemp=... -p no:cacheprovider
  8160 passed, 14 skipped, 2043 warnings in 504.73s (0:08:24)

ruff check .
  All checks passed!
pyright tests/test_skill_no_backtick_substitution.py tools/skillgen/gen.py
  0 errors, 0 warnings, 0 informations

Not run: pyright on the whole repo.

Graphify-specific checklist

  • I updated generated skill artifacts (uv run python -m tools.skillgen --bless) when changing their source fragments.
  • I confirmed that AST/structural extraction remains deterministic (no ambient state dependencies like ENV variables).
  • I reviewed changes for security implications (no unsafe interpolation into shell/Python).
  • I confirmed no API keys or local-only graph data are included.
  • (If applicable) I disclosed AI authorship in my commit messages.

Checks every generated skill file statically, and runs the Part C and
update merge statements under bash with a stub interpreter. This commit
alone fails 58 cases (29 per check); the next commit fixes them.
Graphify-Labs#4461)

Bash expands backticks inside a double-quoted -c block, so two comments
ran graphify update and a command named changed before Python started.
Drop the backticks and keep the words; regenerate with --bless. The
gen.py monolith audit accepts the new Part C comment text.
@github-actions

Copy link
Copy Markdown

Thanks for the pull request, @Deepjyoti-Sarmah. A maintainer will review it soon.

Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions.

A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic.

@github-actions github-actions Bot added the contributor Pull request from a returning community contributor label Oct 14, 2026

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.

Not checked: tests were not run; formal verification proved 0 of 1 changed function(s) (1 sampled, not proven).

Formal verification. PR-changed functions: 1/1 verified (0 proven, 1 may-equivalent, 0 distinguished) · 0 not verified.


Graphify review — findings

Removes the backticks around graphify update in the _origin comment of the semantic-merge snippet in every platform skill (agents, aider, amp, claw, codex, copilot, devin, droid, kilo, kiro, opencode, pi, trae, vscode, windows and the default), so the shell no longer treats them as command substitution inside the double-quoted Python block. Updates the skillgen fragments and expected outputs so regenerated skills keep the plain-text wording. Adds a static test using backtick_lines_in_dq_blocks to flag backticks inside double-quoted shell blocks, though it does not yet cover single-line -c "..." strings.

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1258 functions depend on the 1178 functions this change touches.

Health — grade A; 5 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):

  • render() — 14 callers, 5 callees (high)
  • audit_coverage() — 8 callers, 6 callees (high)
  • main() — 3 callers, 11 callees (medium)
  • monolith_roundtrip() — 3 callers, 5 callees (medium)
  • test_audit_catches_a_dropped_non_allowlisted_heading() — 0 callers, 6 callees (medium)

Verification — 1258 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1258 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

419 of 419 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/pi.test.ts — full-run-safety
  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_defines.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_affected_overrides.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_alias_folding.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_benchmark_real_query.py — full-run-safety
  • tests/test_blade_extractor.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_located_semantic_identity.py — full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_c_include_resolution.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_cache_stale_import_target.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_cargo_missing_manifest.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cloud_cta.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_cluster_ambiguous_scale.py — full-run-safety
  • tests/test_cluster_exclude_hubs.py — full-run-safety
  • tests/test_cobol_extractor.py — full-run-safety
  • … and 369 more

non-code file(s) changed (graphify/skill-agents.md, graphify/skill-aider.md, graphify/skill-amp.md, graphify/skill-claw.md, graphify/skill-codex.md …) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (graphify/skill-agents.md, graphify/skill-aider.md, graphify/skill-amp.md, graphify/skill-claw.md, graphify/skill-codex.md …) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

No difference found (not proven): No behavior difference found in \_is\_semantic\_origin\_stamp\_fix\_line (not a proof).

The verifier ran both versions of \_is\_semantic\_origin\_stamp\_fix\_line on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

@safishamsi

Copy link
Copy Markdown
Member

Landed in 0.9.92 (authorship-preserving cherry-pick, so it shows on your contributions). Thanks @Deepjyoti-Sarmah! Released to PyPI: https://pypi.org/project/graphifyy/0.9.92/ · https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.92

@safishamsi safishamsi closed this Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor Pull request from a returning community contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: backticks in two skill comments make bash run graphify update and changed mid-merge

2 participants