You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Semantic extraction cannot tell a file with no entities from an omitted file, so empty files arm the shrink guard #4547
Semantic extraction cannot tell "the model found no entities in this file" from "the model omitted this file". Both end up in uncovered_files, both make the extraction count as incomplete, and both keep the shrink guard armed, so a file that legitimately has nothing to extract (a robots.txt, an HTML shell, a short compose file) fails a headless graphify extract exactly as a dropped file does, on every run.
Version
graphifyy 0.9.92, graphify extract . --backend claude --model claude-haiku-5-5 --force in CI.
What happens
In llm.py, after the chunks are merged, a dispatched file is "uncovered" when no returned node has it as source_file:
uncovered = sorted(p for p in dispatched if p.resolve() not in {c.resolve() for c in covered})
...
WARNING: N/M dispatched file(s) produced no nodes and are absent from the graph ... The model returned a response but omitted them; a re-run will retry them.
The extraction schema in _EXTRACTION_SYSTEM has no way for the model to say "I read this file and there is nothing to extract": the response is only nodes, edges and hyperedges, and nothing asks for one acknowledgement per file. So absence of nodes is the only signal, and it means two different things.
On our run, 18 of 330 files were uncovered with no truncation and no hollow responses ("the model returned complete, well-formed responses that simply did not address every file"). The ones named include README.md, docker-compose.yml and two index.html files; HTML shells and compose files may well have nothing worth a node. The guard then refused the write, which is correct for omitted files and wrong for empty ones, and a re-run with the same inputs reproduces it.
Two smaller points in the same place:
The warning prints five names and "+N more"; the full list is not written anywhere a CI job can read after the run (it lives in memory as uncovered_files).
A node the model mis-attributes to another file's source_file also leaves the real file uncovered, which is a third case behind the same signal.
Expected
One of:
A per-file acknowledgement in the schema (for example "files": [{"path": "...", "entities": 0}], or a required file-level node per dispatched file), so "read, nothing to extract" is distinguishable from "not addressed", and only the latter counts as incomplete; or
A file-level node always minted for each dispatched document (as the AST pass does for code), so coverage is never zero for a file the model actually read.
And the full uncovered list written to a file in graphify-out/ (or printed in full under a flag), so CI can report it.
Workaround we are using
Keeping non-markdown files out of the semantic pass with .graphifyignore patterns, and a smaller --token-budget so fewer files share a chunk.
Related
#3412 (the unverified-semantic-shrink guard has no verification path), #4546 (thinking on the claude backend).
I reproduced this on the latest v8 (0.9.92). It happens the way the report says: a file with zero nodes always lands in uncovered_files, and that sets _extraction_incomplete. With that set, --force is off, so any run where the graph shrinks exits non-zero. The extraction format gives the model no way to say "I read this file and there's nothing in it."
I have a fix ready on a branch. It follows option 1 from the issue:
The extraction format gets an optional top-level files list, one entry per file in the chunk: {"path": "...", "entities": 0}.
A file with no nodes that's listed with entities: 0 goes into a new empty_files list. It isn't counted as uncovered and doesn't set off the shrink guard.
These cases behave exactly as they do today: a file the model doesn't list, a file listed with entities > 0 but no node behind it, and a response with no files key. Paths that weren't in the chunk are ignored. A file from a truncated or hollow chunk stays uncovered whatever the model says about it.
A response that only acknowledges files is no longer treated as hollow, but only when every file in the chunk is listed as empty. Otherwise a lone robots.txt would be retried twice and then marked partial. If the response leaves out any file in the chunk, it still gets the normal hollow retry.
The full uncovered list is written to graphify-out/uncovered_files.txt, so CI can show it. A stale copy is removed on the next run.
The warning no longer says a re-run will fix it. Re-sending the same chunk usually skips the same files.
Limits I know about:
The guard now trusts the model's 0. If the model writes nodes for some files and falsely lists the rest as 0, those files won't trigger the guard. The omission in this report (files never mentioned) is still caught.
If a large document is split into slices and one slice reports 0 while another slice is never mentioned, the file counts as empty.
references/extraction-spec.md has no files rule yet, so skill-driven runs behave as they do today.
The full suite passes, and there's a new test file covering each case above. I'll open the PR unless you'd rather take a different direction, such as option 2 (always creating a file-level node).
Activity
github-actions commentedon Oct 18, 2026
Thanks for opening this issue, @tobimax. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
Deepjyoti-Sarmah commentedon Oct 19, 2026
I reproduced this on the latest v8 (0.9.92). It happens the way the report says: a file with zero nodes always lands in uncovered_files, and that sets _extraction_incomplete. With that set, --force is off, so any run where the graph shrinks exits non-zero. The extraction format gives the model no way to say "I read this file and there's nothing in it."
I have a fix ready on a branch. It follows option 1 from the issue:
Limits I know about:
The full suite passes, and there's a new test file covering each case above. I'll open the PR unless you'd rather take a different direction, such as option 2 (always creating a file-level node).