Skip to content

fix: fail closed before project install mutations - #2430

Open
SomSamantray wants to merge 7 commits into
Graphify-Labs:v8from
SomSamantray:fix/fail-closed-project-install
Open

fix: fail closed before project install mutations#2430
SomSamantray wants to merge 7 commits into
Graphify-Labs:v8from
SomSamantray:fix/fail-closed-project-install

Conversation

@SomSamantray

Copy link
Copy Markdown

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.md entry, 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

  • Focused install, merge, and round-trip tests: 183 passed
  • pyright graphify/install.py: 0 errors
  • Ruff checks for changed Python files: passed
  • Full suite with the optional openai dependency: 3928 passed, 36 skipped; one unrelated failure remains in tests/test_labeling.py::test_label_communities_batches_when_over_batch_size because 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:

config = preflight(config_path)  # Reject bad input before any project writes.
copy_skill()
write_plugin(config)

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.


Compound Engineering

@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.

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).

Comment thread graphify/install.py
@@ -692,7 +697,14 @@ def _gemini_hook() -> dict:
}
def gemini_install(project_dir: Path | None = None, *, project: bool = False) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiongemini_install()

fans out to 7 callees (efferent coupling); 10 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/install.py
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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_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.

Comment thread graphify/install.py
_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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionclaude_install()

19 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/install.py
@@ -1887,6 +2009,13 @@ def _strip_graphify_md_section(target: Path) -> bool:
return True
def codebuddy_install(project_dir: Path | None = None) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressioncodebuddy_install()

22 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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.

Codex project install mutates skill and AGENTS.md before validating hooks.json

1 participant