fix: fail closed before project install mutations - #2430
Conversation
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR adds a new design/planning document at docs/plans/2026-08-03-001-fix-fail-closed-project-install-plan.md describing an approach to make project-scoped Graphify installs "fail closed" — validating existing platform configuration files before any Graphify-owned mutation occurs. The intended change spans the installer logic in graphify/install (Codex, Claude, Gemini, OpenCode, Kilo, Kilo/Cursor uninstall, Amp, and JSON-loading helpers) and adds corresponding preflight/failure-path and merge-behavior tests across tests/test_install and tests/test_settings_merge. The surface area touches multiple platform installer entrypoints, shared JSON/JSONC parsing utilities, skill-file handling, and settings-merge behavior.
No blocking issues surfaced.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 519 functions depend on the 347 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
codebuddy_install()— 22 callers, 5 callees - worse:
claude_install()— 19 callers, 4 callees - worse:
gemini_install()— 10 callers, 7 callees - worse:
install()— 5 callers, 12 callees - worse:
_project_install()— 4 callers, 14 callees - worse:
_agents_install()— 6 callers, 9 callees
Verification — 519 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: 468 function(s) in the blast radius were not formally verified this run
· 4 grounded finding(s) anchored inline below; 2 more finding(s) on lines outside this diff (see the check run).
| @@ -692,7 +697,14 @@ def _gemini_hook() -> dict: | |||
| } | |||
| def gemini_install(project_dir: Path | None = None, *, project: bool = False) -> None: | |||
There was a problem hiding this comment.
gemini_install()
fans out to 7 callees (efferent coupling); 10 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| hooks_path.write_text(json.dumps(existing, indent=2), encoding="utf-8") | ||
| print(f" .codex/hooks.json -> PreToolUse hook removed") | ||
| def _agents_install(project_dir: Path, platform: str) -> None: | ||
| def _agents_install( |
There was a problem hiding this comment.
_agents_install()
fans out to 9 callees (efferent coupling); 6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _agents_uninstall(project_dir or Path("."), platform="kilo") | ||
| removed = _kilo_uninstall_global() | ||
| print("; ".join(removed) if removed else "nothing to remove") | ||
| def claude_install(project_dir: Path | None = None, strict: bool = False) -> None: |
There was a problem hiding this comment.
claude_install()
19 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1887,6 +2009,13 @@ def _strip_graphify_md_section(target: Path) -> bool: | |||
| return True | |||
| def codebuddy_install(project_dir: Path | None = None) -> None: | |||
There was a problem hiding this comment.
codebuddy_install()
22 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
Project installs now fail before changing Graphify-owned files when an existing platform configuration is malformed. Previously, an invalid configuration could leave behind a partially installed skill,
AGENTS.mdentry, command, or plugin; the installer now validates the configuration and managed structure first, then reuses the validated state for writes. The safety contract covers combined Codex, Claude, Gemini, CodeBuddy, OpenCode, and Kilo project flows while preserving skill-only routes and existing JSON/JSONC formats.Session-settled decisions carried from planning: preflight-first validation (user-approved, over a full transaction layer).
Fixes #2416
Validation
pyright graphify/install.py: 0 errorsopenaidependency: 3928 passed, 36 skipped; one unrelated failure remains intests/test_labeling.py::test_label_communities_batches_when_over_batch_sizebecause its observed batch order is[100, 50, 100]instead of[100, 100, 50]New concepts
Side-effect-free preflight validation
Preflight validation checks an existing configuration before an installer performs any mutation, and can pass the parsed result into the write phase.
Why this change uses it:
This is safer than adding rollback for a multi-file install: malformed input is deterministic, while rollback would need to preserve user files and account for partial I/O. The project routes apply the pattern before skills, instructions, commands, plugins, and configuration writes; skill-only routes do not read unrelated configuration. Do not use this pattern to broaden validation into flows that neither read nor mutate the configuration.