Skip to content

fix(claude-cli): deliver extraction instructions in the user turn (fixes infinite bisection on Claude Code >= 2.1) - #1611

Closed
javargasm wants to merge 1 commit into
Graphify-Labs:v8from
javargasm:fix/claude-cli-system-prompt-ignored
Closed

fix(claude-cli): deliver extraction instructions in the user turn (fixes infinite bisection on Claude Code >= 2.1)#1611
javargasm wants to merge 1 commit into
Graphify-Labs:v8from
javargasm:fix/claude-cli-system-prompt-ignored

Conversation

@javargasm

Copy link
Copy Markdown
Contributor

Summary

The claude-cli backend can hang on an infinite chunk bisection under newer Claude Code CLIs (>= ~2.1, verified on 2.1.197), so graphify extract --backend claude-cli never writes graph.json.

_call_claude_cli delivered the extraction schema via --system-prompt and put only the raw file dump in the user turn, on the assumption that a replacement system prompt is the model's sole authority. Claude Code >= 2.1 does not honour that: it still layers in the local coding-agent context (CLAUDE.md/AGENTS.md in cwd, skills, MCP) and, given a user turn that is just a file with no request, replies conversationally:

"I see the file, but there's no actual request attached — what would you like me to do with it?"

That prose parses to zero nodes/edges, so _response_is_hollow flags it as truncation and the adaptive-retry path bisects the chunk indefinitely (94 → 47 → 23 → …), never converging and never writing graph.json. The run ends in all semantic chunks failed for backend 'claude-cli'.

Observed log:

[graphify] LLM returned invalid JSON, skipping chunk (first 200 chars:
  'I see the file ... but no actual question or task attached...')
[graphify] claude-cli returned a hollow response; treating as truncation ...
[graphify] chunk of 94 truncated at depth 0, splitting into halves of 47 and 47
... 47 -> 23 -> 11 ...

Fix

Move the full extraction schema plus an explicit imperative into the user turn and drop --system-prompt, so the CLI emits the JSON object directly. The <untrusted_source> prompt-injection guardrails are carried verbatim (the schema text is unchanged; only its delivery channel moves). Model override (GRAPHIFY_CLAUDE_CLI_MODEL), --add-dir image handling, timeout, and token accounting are untouched.

Why not other approaches

Verified empirically before landing this:

  • Model isn't the lever--model haiku fails the same way.
  • cwd isn't the lever — running from a clean dir with no CLAUDE.md still returns prose.
  • Isolation flags don't work--setting-sources "" / --tools "" / --mcp-config '{}' break auth (Not logged in) or hang.

Moving instructions into the user turn is the only approach that makes the CLI emit JSON.

Verification

  • Real extract on 4 repos (Python/TS/YAML) now writes graph.json with no bisection: e.g. 38 nodes / 38 edges, 3468 / 11390, 679 / 1334, 3723 / 7302.
  • The pre-existing test_uses_system_prompt_not_append (which asserted the old behaviour that caused this bug) is updated to assert instructions ride in the user turn.
  • Added regression tests: --system-prompt absent from argv; schema + imperative + source present in stdin; <untrusted_source> guardrails preserved.
  • pytest tests/test_claude_cli_backend.py tests/test_llm_parser.py tests/test_llm_backends.py116 passed.

Notes

  • The est. cost (~claude-cli): $0.0000 line is unaffected by this change, but note extraction runs on the Claude Code plan (Opus by default); GRAPHIFY_CLAUDE_CLI_MODEL=haiku reduces that.

The claude-cli backend passed the extraction schema via --system-prompt with
only the raw file dump in the user turn, assuming a replacement system prompt
is the model's sole authority. Claude Code >= ~2.1 (verified on 2.1.197) does
not honour that: it still layers in the local coding-agent context
(CLAUDE.md/AGENTS.md in cwd, skills, MCP) and, given a user turn that is just a
file with no request, replies conversationally ("I see the file, but there's no
actual request attached"). That prose parses to zero nodes/edges, so
_response_is_hollow flags it as truncation and the adaptive-retry path bisects
the chunk indefinitely (94 -> 47 -> 23 -> ...), never converging and never
writing graph.json.

Move the full extraction schema plus an explicit imperative into the user turn
and drop --system-prompt, so the CLI emits the JSON object directly. The
<untrusted_source> prompt-injection guardrails are carried verbatim; model
override, --add-dir image handling, timeout, and token accounting are untouched.
@safishamsi

Copy link
Copy Markdown
Collaborator

Merged into v8 as 32ff6d6 (your authorship). Nicely diagnosed — the CC >=2.1 conversational-reply-to-bare-file-dump causing _response_is_hollow to bisect forever is exactly the failure. Verified --system-prompt is dropped and the extraction schema (with the <untrusted_source> guardrails intact) now rides in the user turn. High-impact for anyone on the default claude-cli backend post-2.1. Full suite 2841. Ships next release.

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.

2 participants