Skip to content

graft init --dry-run ignores suppression flags and omits files changed by init #329

Open

Description

@klaatu-code

Summary

graft init --dry-run does not currently describe the operation that a real init would perform:

  1. The plan ignores --no-global, --no-mcp, and --no-hooks for non-Claude hosts.
  2. It omits other files that init can create, modify, or delete, including the wiring stamp, retraction targets, and graph-build side effects.

This makes --dry-run unsafe as a preflight check, especially because its help text says it will "print every file init would touch."

Environment

  • @nanonets/graft 0.16.0
  • macOS 26.6.2, arm64
  • Node.js 24.17.0
  • Also verified against current main at 3a85a2f40e283ac0c9b617d9a4bead18d73c3c70

Reproduction: suppression flags are not reflected

Run these in a repository where the agents host is available:

graft init --dry-run --agents agents --no-build
graft init --dry-run --agents agents --no-build --no-global --no-mcp --no-hooks

On 0.16.0, both commands print the same planned writes. The second command still reports:

would write — this repo:
  AGENTS.md      fenced graft section
  opencode.json  mcp.graft

would write — your machine, affects ALL repos:
  ~/.codex/config.toml                  [mcp_servers.graft]
  ~/.codex/hooks/graft/graft-hooks.cjs  post-edit hook shim
  ~/.codex/hooks.json                   SessionStart / UserPromptSubmit / PostToolUse / Stop

The real writer does apply these flags:

  • runHostsInit() skips MCP writes when mcp === false.
  • It skips global Codex hooks when hooks === false or global === false.
  • MCP config writers receive the global option.

The dry-run path does not. On current main, src/cli.ts calls planInit(repo, { home }), while src/hosts/plan.ts adds MCP and hook targets unconditionally.

Reproduction: the plan is incomplete

Even after the flag mismatch is fixed, the plan does not include all paths a real run may touch:

  • writeStamp() writes graft/.cache/wiring-stamp.json, including with --no-build.
  • runRetract(..., { apply: true, exclude: ids }) can modify or delete wiring for previously selected hosts before the new wiring is written.
  • When the graph is missing and --no-build is not used, buildGraphIfMissing() runs graft build, which creates the generated graft/ tree and may update .gitignore and .ignore.

The dry-run branch returns before these operations and does not report them.

Expected behavior

--dry-run should match the real operation for the same arguments:

  • Omit targets disabled by --no-global, --no-mcp, and --no-hooks.
  • Report files that would be modified or deleted by retraction.
  • Report the wiring stamp and graph/build outputs when applicable, or explicitly label generated output directories/side effects if enumerating every generated file is impractical.

An alternative would be to narrow the command/help contract and clearly state that the output covers only agent wiring targets, but it should still apply the suppression flags accurately.

Suggested implementation direction

  • Pass global, mcp, hooks, and build into the planning layer.
  • Build non-Claude host plans through the same predicates used by runHostsInit().
  • Reuse planRetract() with apply: false and the same exclude/global options to include removals.
  • Add the wiring stamp and graph-build side effects to the plan.
  • Add a flag-matrix test comparing the planned target set with an instrumented real writer.

Related work

#118 and draft PR #124 address the narrower issue where Claude Code itself ignores --no-mcp / --no-hooks. PR #124 makes Claude targets aware of those two flags, but its non-Claude HOSTS.map(...) plan still adds MCP/hooks unconditionally, does not model --no-global, and does not include retraction, stamp, or build side effects. This report is intended to track the remaining dry-run contract rather than duplicate #118.

Activity

  1. djbclark commented on Sep 26, 2026

    @djbclark

    Confirming the retraction gap on 0.20.0 with a concrete case, since it is the most surprising of the omissions listed here.

    In a repo already wired for Claude Code (project .mcp.json + user-level ~/.claude.json / ~/.claude/settings.json hooks), adding one more host:

    graft init --agents hermes --no-build --dry-run
    

    prints only

    would write — this repo:
      AGENTS.md  fenced graft section
    

    The real run then removes, via runRetract(..., { exclude: ids }): the project .mcp.json (deleted, graft was its only key), mcpServers.graft in ~/.claude.json, the four graft hooks and helpers/graft-hooks.cjs under ~/.claude, ~/.codex/hooks.json entries + ~/.codex/hooks/graft/, [mcp_servers.graft] in ~/.codex/config.toml, and ~/.gemini/skills/graft/SKILL.md. None of those appear in the plan, and several are scope: 'global', so the dry run also fails to show that a repo-scoped command is about to edit files that affect every repo.

    planRetract() with apply: false and the same exclude/global options, as suggested above, would have listed all of them. I filed the documentation / blast-radius side separately in #482 so this one can stay about the dry-run contract.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions