Skip to content

fix(tools): stop the MCP repo generator from overwriting evolved repos - #309

Merged
piyushmakhija28 merged 1 commit into
mainfrom
fix/generator-non-destructive
Aug 10, 2026
Merged

piyushmakhija28 merged 1 commit into
mainfrom
fix/generator-non-destructive

Conversation

@piyushmakhija28

Copy link
Copy Markdown
Collaborator

The hazard

scripts/tools/create_mcp_repos.py was written as a one-time bootstrap and was never guarded against re-runs. Running it against a repo that already exists:

Line Effect
:778 overwrites server.py
:786-787 rmtree(base/) then copytree
:791-795 overwrites the SHARED_UTILS files
:798-802 rewrites README.md, CLAUDE.md, requirements.txt, .gitignore, .env.example
:811-815 git add -A + git commit -m "feat: initial commit -- ..."
:819-833 gh repo create --push, or git push -u origin main

So it does not merely overwrite locally — it commits the overwrite and pushes it to origin. History survives (an ordinary commit on top, not a force-push), but HEAD moves to the stale generated version.

Blast radius

Measured 2026-08-10: all 13 target repos have evolved past their generated state — 75 commits and roughly 29 merged PRs between them. This was the whole MCP fleet, not one repo.

The fix

  • Existing repos are skipped unless --force is passed.
  • A plain run exits 1 rather than reporting success, so re-running cannot look like a no-op.
  • --dry-run reports the decision for every target and writes nothing.
  • mcp-session-mgr is marked canonical and is never regenerated, even with --force.

Why mcp-session-mgr is canonical

It is the server settings.json actually runs, and it leads the in-engine src/mcp/session_mcp_server.py by mcp 2.x support, an O_CREAT|O_EXCL claim-file mutex, and a merged path-traversal fix. Regenerating it from src/mcp/ would silently revert all three — including the security fix — into the only path Claude calls.

CANONICAL_REPOS is a set, so removing a name from it is an explicit assertion that src/mcp/ is once again ahead.

Verification

Check Result
Plain run writes nothing 13 repo HEADs and working trees byte-identical before and after
Plain run exit code 1
--dry-run exit code 0, 14 skipped, 0 written
Canonical guard under --force refused
Ordinary existing repo under --force proceeds
Bootstrap (repo absent) proceeds
ruff / black / isort pass

--force was deliberately not run end-to-end, since doing so would overwrite the other twelve repos. The canonical guard was exercised by calling _guard_existing_repo directly.

Context

This is P1.0 of the session-management remediation — a blocking precondition for Phase 1. create_mcp_repos.py was DO-NOT-RUN until this landed. Recorded in docs/policies/session-management-policy.md §7.

🤖 Generated with Claude Code

create_mcp_repos.py was written as a one-time bootstrap and was never
guarded against re-runs. Running it against an existing repo replaced
server.py, deleted and recreated base/, rewrote the generated docs,
committed the result as "initial commit", and pushed it to origin.
History survived -- it is an ordinary commit on top, not a force-push --
but HEAD moved to the stale generated version.

Measured 2026-08-10: all 13 target repos have evolved past their
generated state, 75 commits and roughly 29 merged PRs between them. The
blast radius was the whole MCP fleet, not a single repo.

Existing repos are now skipped unless --force is passed, and a plain run
exits 1 instead of reporting success, so re-running cannot look like a
no-op. --dry-run reports the decision for every target and writes nothing.

mcp-session-mgr is additionally marked canonical and is never regenerated
even with --force. It is what settings.json runs, and it leads the
in-engine copy by mcp 2.x support, an O_CREAT|O_EXCL claim-file mutex and
a merged path-traversal fix -- all three of which regeneration would
silently revert.

Verified: a plain run writes nothing (repo HEADs and working trees
identical before and after) and exits 1; --dry-run exits 0; the canonical
guard holds under --force.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@piyushmakhija28
piyushmakhija28 force-pushed the fix/generator-non-destructive branch from 67ee2db to 58fd3ae Compare August 10, 2026 13:47
@piyushmakhija28
piyushmakhija28 merged commit 53ec3d6 into main Aug 10, 2026
6 checks passed
@piyushmakhija28
piyushmakhija28 deleted the fix/generator-non-destructive branch August 10, 2026 13:56
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.

1 participant