Conversation
graph.json is pretty-printed and its node ordering shifts between rebuilds, so a rebuild of a committed graphify-out/ can present a million changed lines. GitHub gives up computing a diff that size, answers 422, and the pull request then reports zero changed files for every file in it, not only this one: the Files changed tab goes empty and the API agrees with it. Write a .gitattributes into the output directory when it has none, marking the regenerated artifacts `-diff` so git never computes that diff, and `linguist-generated` so the repo's language stats ignore them. `linguist-generated` alone does not help here, since it only collapses the rendered diff after it has been computed. Only the output directory gets one: `--graph` can name any path, and dropping a file into a directory the user chose for one artifact would be writing somewhere we were not invited. An existing .gitattributes is never touched, GRAPHIFY_NO_GITATTRIBUTES=1 turns it off the way GRAPHIFY_NO_BACKUP does, and a directory that refuses the write costs the attributes file rather than the build.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Writes a .gitattributes into the graphify output directory on to_json, marking graph.json, manifest.json, GRAPH_REPORT.md, and .graphify_labels.json as -diff (plus * linguist-generated) so a rebuild's reordered pretty-printed JSON can't trigger GitHub's 422-on-huge-diff and blank out the whole PR's file changes. Only touches directories named GRAPHIFY_OUT_NAME, so a bare --graph path is left alone, an existing .gitattributes is never overwritten, and GRAPHIFY_NO_GITATTRIBUTES=1 opts out. Failure to write (e.g. read-only dir) is swallowed via ensure_output_gitattributes returning None rather than failing the build.
Worth a look
- Dangling .gitattributes symlink is followed and writes outside output dir —
graphify/export.py:66· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 604 functions depend on the 177 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_rebuild_code()— 116 callers, 51 callees - new:
to_obsidian()— 38 callers, 14 callees - new:
to_json()— 58 callers, 8 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
_make_graph()— 33 callers, 6 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
to_canvas()— 20 callers, 5 callees - new:
to_graphml()— 15 callers, 5 callees - …and 3 more — each is listed as a finding
Verification — 604 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: 429 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
21 of 275 test file(s) selected (8%) via static blast radius.
tests/test_atomic_canvas_export.py— impacttests/test_atomic_writes.py— impacttests/test_build.py— impacttests/test_carried_hyperedge_remap.py— impacttests/test_cli_export.py— impacttests/test_community_labels_skill.py— impacttests/test_confidence.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_export.py— impact, changed-testtests/test_export_control_characters.py— impacttests/test_export_path_length.py— impacttests/test_go_qualified_resolution.py— impacttests/test_god_nodes_exclude_hubs.py— impacttests/test_hyperedge_roundtrip.py— impacttests/test_hypergraph.py— impacttests/test_obsidian_dangling_member.py— impacttests/test_obsidian_filename_cap.py— impacttests/test_obsidian_unicode_tags.py— impacttests/test_obsidian_vault_migration.py— impacttests/test_pipeline.py— impacttests/test_reflect.py— impact
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 to\_json (not a proof).
The verifier ran both versions of to\_json 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.
· 11 more finding(s) on lines outside this diff (see the check run).
Closes #3413.
A committed
graphify-out/can cost a pull request its whole diff.graph.jsonis pretty-printed and node ordering shifts between rebuilds, so a rebuild presents a very large changed-line count. GitHub gives up computing a diff that size and answers 422, and the pull request then reports zero changed files for every file in it, not only for the artifacts. The Files changed tab goes empty and the API agrees with it, which is what the reporter is looking at in the screenshot.The reporter had already worked out the right incantation by hand. This ships it, so the next person does not have to.
The change
to_jsonwrites a.gitattributesinto the output directory when it has none:-diffis the part that matters. It makes git treat these as opaque, so the diff is never computed.linguist-generatedonly collapses the rendered result, which is too late: the computation has already happened and already timed out. The repo already carriesgraphify-out/**/*.html linguist-vendored=truein its own root.gitattributes, so marking these artifacts is an established position here, not a new one.Three boundaries, each with a test:
--graphcan name any path, and dropping a file into a directory the user chose for one artifact would be writing somewhere we were not invited. The write is gated on the parent directory beingGRAPHIFY_OUT_NAME..gitattributesis left exactly as it is, so a user who has tuned theirs keeps it.backup_if_protectedtakes.GRAPHIFY_NO_GITATTRIBUTES=1turns it off, mirroringGRAPHIFY_NO_BACKUP.Test
Five cells in
tests/test_export.py. Four fail onv8:The fifth,
test_to_json_leaves_a_directory_it_was_only_pointed_at_alone, passes before and after on purpose: it pins that this does not start writing into arbitrary--graphtargets.tests/test_export.pyis green at 65; the export, cli-export and atomic slices at 326 passed, 25 skipped. The fulltests/run is 5255 passed with 14 failures, all of which also fail on unmodifiedv8here (terraform, ollama retry, markdown frontmatter, the wheel payload and a TS timing test) and none of which touch this path.Mutation-checked: six seeded defects, including removing the call, dropping the output-directory guard, overwriting an existing file, ignoring the opt-out, letting a read-only directory raise, and dropping the
-diffline. Each is caught by a named cell; none survive.No formatter was run over the file, so the diff is only these lines.