Skip to content

fix(mcp): register Claude Code Context7 MCP in ~/.claude.json - #1909

Merged
Alan-TheGentleman merged 1 commit into
Gentleman-Programming:mainfrom
Denver2828:fix/1868-claude-mcp-user-config
Jul 30, 2026
Merged

fix(mcp): register Claude Code Context7 MCP in ~/.claude.json#1909
Alan-TheGentleman merged 1 commit into
Gentleman-Programming:mainfrom
Denver2828:fix/1868-claude-mcp-user-config

Conversation

@Denver2828

@Denver2828 Denver2828 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

🔗 Linked Issue

Closes #1868


🏷️ PR Type

  • type:bug.. Bug fix (non-breaking change that fixes an issue)

📝 Summary

Claude Code solo lee registraciones MCP de user scope desde ~/.claude.json. Hoy gentle-ai escribe context7 en ~/.claude/settings.json (clave mcpServers que Claude Code ignora en ese archivo), asi que el server instalado nunca aparece en Claude Code.

Este PR es el primer paso de un split acordado en la review (ver roadmap en el hilo): registra context7 en ~/.claude.json con un merge no destructivo.. base corrupta aborta (nunca resetea el archivo que carga la sesion OAuth), retry si la base cambia debajo, modo 0600 forzado siempre.. limpia best-effort el bloque inerte de settings.json, apunta la verificacion post-install al registry real, y el uninstall nunca borra el archivo aunque quede vacio. Engram, el lock cross-proceso y el resto del lifecycle vienen en los PRs siguientes del roadmap.


📂 Changes

File / Area What Changed
internal/agents/claude/adapter.go UserConfigPath y MergeUserConfig (parse estricto, retry ante base movida, 0600 incondicional)
internal/components/mcp/inject.go context7 para Claude va a ~/.claude.json; limpieza best-effort del bloque inerte en settings.json
internal/components/uninstall/service.go cleanup de mcpServers.context7 en el registry via rewriteClaudeUserConfig, que nunca borra el archivo y preserva el modo
internal/cli/run.go la verificacion post-install de context7/Claude apunta a ~/.claude.json (el write target real)
tests idempotencia byte a byte + re-tighten 0600 en run no-op, refusal ante base corrupta, cleanup inerte (tabla managed/foreign), registry sobrevive al ultimo server removido, wiring de verificacion

🧪 Test Plan

Unit Tests

go test ./...

Go Format

go run ./internal/gofmtcheck

E2E Tests (Docker required)

cd e2e && ./docker-test.sh
  • Unit tests pass (go test ./...)
  • Go format passes (go run ./internal/gofmtcheck)
  • E2E tests pass (cd e2e && ./docker-test.sh).. corren en CI; sin Docker en mi maquina Windows
  • Manually tested locally

Prueba manual: instale gentle-ai v2.2.0 en mi maquina y el sync re-creo el bloque inerte en settings.json; con este branch la registracion queda en ~/.claude.json y Claude Code la lee.


✅ Contributor Checklist

  • PR is linked to an issue with status:approved
  • PR stays within 400 changed lines, or I have requested/obtained maintainer-applied size:exception with rationale documented.. 400 exactas (364+36) tras el split
  • I have added the appropriate type:* label to this PR
  • Unit tests pass (go test ./...)
  • Go format passes (go run ./internal/gofmtcheck)
  • E2E tests pass (cd e2e && ./docker-test.sh).. via CI
  • I have updated documentation if necessary
  • My commits follow Conventional Commits format
  • My commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

Rebaseado sobre main post-v2.2.2 y recortado al nucleo de context7 siguiendo el pedido de split de la review. El roadmap completo del split (engram, lock advisory + CodeGraph, backup/#1794, y los issues de diseño para workspace-scope, TOCTOU contra Claude Code y ACLs de Windows) esta detallado en el comentario de respuesta a la ultima review.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Claude Code MCP injection now writes Context7 and Engram registrations to ~/.claude.json, preserves existing configuration, rejects corrupt JSON, removes legacy files, and extends uninstall cleanup for the new locations.

Changes

Claude Code MCP configuration

Layer / File(s) Summary
User-config merge contract
internal/agents/claude/adapter.go
Adds guarded, atomic merging for ~/.claude.json, including concurrent-change retries, corruption rejection, and 0600 permissions.
Context7 user-config injection
internal/components/mcp/inject.go, internal/components/mcp/inject_test.go
Context7 is merged into ~/.claude.json; inert settings entries are conditionally removed and behavior is covered by idempotency and corruption tests.
Engram user-config migration
internal/components/engram/inject.go, internal/components/engram/inject_test.go
Engram commands are preserved or migrated into ~/.claude.json, --tools=agent is maintained, and legacy files are removed.
Claude Code uninstall cleanup
internal/components/uninstall/service.go
Uninstall rewrites Claude user-config entries, removes legacy managed files, and preserves existing file permissions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Injector
  participant ClaudeUserConfig
  participant LegacyMCPFiles
  participant Uninstaller
  Injector->>ClaudeUserConfig: merge Context7 and Engram registrations
  Injector->>LegacyMCPFiles: remove legacy managed files
  Uninstaller->>ClaudeUserConfig: rewrite managed MCP entries
  Uninstaller->>LegacyMCPFiles: remove remaining legacy files
Loading

Possibly related PRs

Suggested labels: type:bug, size:exception

Suggested reviewers: alan-thegentleman

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies #1868 by writing Context7 and Engram to ~/.claude.json, preserving existing data, and failing on corrupt configs.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes are evident; the additional cleanup, permission, retry, and uninstall updates support the same Claude Code config fix.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes a major part of the change: moving Claude Code Context7 MCP registration to ~/.claude.json.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/components/mcp/inject_test.go (1)

481-533: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a file-mode assertion for ~/.claude.json.

The injector deliberately writes 0600 because this file carries the OAuth session, but no test locks that in — a future change to WriteFileAtomic or the mode argument would silently widen it to 0644. The seed file here is already written 0600, so the check is cheap.

🔒 Assert the restrictive mode survives injection
 	raw, err := os.ReadFile(userConfigPath)
 	if err != nil {
 		t.Fatalf("ReadFile(user config) error = %v", err)
 	}
+	info, err := os.Stat(userConfigPath)
+	if err != nil {
+		t.Fatalf("Stat(user config) error = %v", err)
+	}
+	if mode := info.Mode().Perm(); mode != 0o600 {
+		t.Fatalf("~/.claude.json mode = %O; want 0600 (holds the OAuth session)", mode)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/mcp/inject_test.go` around lines 481 - 533, Extend
TestInjectClaudeWritesUserConfigAndIsIdempotent to stat ~/.claude.json after
injection and assert its permission mode remains 0600. Use the existing
userConfigPath symbol and fail the test with the observed mode if the file is
missing or has broader permissions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/engram/inject_test.go`:
- Around line 57-79: Add engram-side test coverage for the strict parse guard in
Inject, mirroring the existing corrupt-user-config refusal test. Create a
corrupt ~/.claude.json, call Inject with claudeAdapter(), assert it returns an
error, then verify the file remains byte-identical to the original corrupt
bytes.

In `@internal/components/engram/inject.go`:
- Around line 320-343: Extract the duplicated Claude user-config merge flow into
a shared filemerge helper, such as MergeIntoClaudeUserConfig, preserving the
existing read, strict JSON-parse guard, error semantics, overlay merge, and
atomic 0600 write behavior. Replace the inline logic in the ClaudeCode branch of
inject.go and injectClaudeUserConfig in the MCP injector with calls to this
helper, retaining each caller’s changed-state and file-list handling.

In `@internal/components/mcp/inject.go`:
- Around line 299-302: Update injectClaudeUserConfig around the
osReadFile(settingsPath) call so settings.json read failures are treated as
best-effort cleanup failures rather than returned to the caller. Preserve the
successful ~/.claude.json registration and continue without attempting the
inert-block cleanup, matching the existing tolerance for unparsable settings
content.

In `@internal/components/uninstall/service.go`:
- Line 741: The Claude configuration rewrites in uninstall must preserve the
existing 0600 permissions of the OAuth-session file. Update both rewriteJSONFile
calls in internal/components/uninstall/service.go at lines 741 and 792-794 to
preserve the existing mode or explicitly use 0600 for
claude.UserConfigPath(homeDir); apply the same change at both sites.

---

Outside diff comments:
In `@internal/components/mcp/inject_test.go`:
- Around line 481-533: Extend TestInjectClaudeWritesUserConfigAndIsIdempotent to
stat ~/.claude.json after injection and assert its permission mode remains 0600.
Use the existing userConfigPath symbol and fail the test with the observed mode
if the file is missing or has broader permissions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b5884d7d-82f9-402d-80e3-df452cf881c1

📥 Commits

Reviewing files that changed from the base of the PR and between b366f7b and 2403d13.

📒 Files selected for processing (6)
  • internal/agents/claude/adapter.go
  • internal/components/engram/inject.go
  • internal/components/engram/inject_test.go
  • internal/components/mcp/inject.go
  • internal/components/mcp/inject_test.go
  • internal/components/uninstall/service.go

Comment thread internal/components/engram/inject_test.go Outdated
Comment thread internal/components/engram/inject.go Outdated
Comment thread internal/components/mcp/inject.go
Comment thread internal/components/uninstall/service.go Outdated
@Denver2828

Copy link
Copy Markdown
Contributor Author

Los cuatro hallazgos eran validos y estan resueltos en c5c1b07:

  1. Helper compartido: claude.MergeUserConfig es ahora el unico punto de enforcement (lectura + guard estricto + merge + escritura 0600) y ambos inyectores lo consumen — la duplicacion desaparecio.
  2. Cleanup best-effort: un settings.json ilegible ya no descarta la registracion exitosa en ~/.claude.json; el bloque es inerte alli de todos modos.
  3. Permisos en uninstall: rewriteJSONFile preserva el modo existente del archivo, asi el 0600 de ~/.claude.json nunca se ensancha a 0644. (El rewriteSkillRegistryHook de settings.json queda en 0644, que es su modo correcto.)
  4. Tests: agregado el rechazo de base corrupta del lado de engram (byte-identico) y la asercion de modo 0600 — con un guard runtime.GOOS != "windows" que el diff sugerido no tenia: en Windows Go reporta los ACL de NTFS como 0666 y la asercion cruda rompia el CI de Windows.

Nota de tamano: el diff paso de 400 exactas a 430 con estos cambios pedidos por el review. Si prefieren que lo baje recortando comentarios aviso, o queda a criterio de ustedes el size:exception.

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/agents/claude/adapter.go`:
- Around line 101-112: Serialize the full read-merge-write sequence around raw
file loading, JSON merging, and WriteFileAtomic in the adapter’s injection
method. Use a shared cross-process lock (or equivalent conflict/retry mechanism)
so concurrent injections cannot merge from stale content or overwrite
registrations; preserve the existing validation and error handling within the
protected operation.

In `@internal/components/mcp/inject_test.go`:
- Around line 510-514: Update the fixture setup in the injection test to create
~/.claude.json with insecure 0644 permissions, then retain the existing
post-injection assertion in the relevant test to require 0600 on non-Windows
systems. Ensure the test verifies tightening an existing file rather than merely
preserving already-secure permissions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 66e1ae29-a1bb-4d35-b43c-03ac62a15823

📥 Commits

Reviewing files that changed from the base of the PR and between 2403d13 and c5c1b07.

📒 Files selected for processing (6)
  • internal/agents/claude/adapter.go
  • internal/components/engram/inject.go
  • internal/components/engram/inject_test.go
  • internal/components/mcp/inject.go
  • internal/components/mcp/inject_test.go
  • internal/components/uninstall/service.go

Comment thread internal/agents/claude/adapter.go Outdated
Comment thread internal/components/mcp/inject_test.go
@Denver2828

Copy link
Copy Markdown
Contributor Author

Ronda 2, un fix y una declinacion razonada:

Permisos (aplicado): el fixture ahora siembra ~/.claude.json con 0644 y la asercion exige 0600 post-inyeccion — se prueba el endurecimiento, no solo la preservacion. Pusheado.

Lock cross-process (declinado, con razones): la carrera read-merge-write que señalas es real pero preexistente y sistemica — cada mergeJSONFile del codebase (opencode.json, settings de Gemini, mcp.json de Cursor, etc.) tiene exactamente la misma ventana, y #1809 ya trackea esa clase para state.json con la misma conclusion: necesita un mecanismo compartido, no un lock artesanal por archivo. Dentro de un mismo run los dos inyectores corren secuenciales, asi que la ventana solo existe entre dos procesos gentle-ai simultaneos, que es el escenario de #1809. Meter aca un lock exclusivo para ~/.claude.json duplicaria infraestructura que deberia salir de esa resolucion, y este PR quedaria acoplado a una decision de diseño que no le pertenece. Si los maintainers prefieren, lo anoto como follow-up explicito alli.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/components/mcp/inject_test.go (1)

499-505: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert byte-level idempotency.

The test only checks second.Changed == false; it never verifies that the second invocation leaves ~/.claude.json unchanged. Capture the file after the first injection and compare it byte-for-byte with the contents after the second injection so rewrites that incorrectly report Changed == false cannot pass.

As per path instructions, install/sync operations must be idempotent (running twice equals running once).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/mcp/inject_test.go` around lines 499 - 505, Update the
idempotency test around the second Inject call to capture the ~/.claude.json
bytes immediately after the first injection, then read the file again after the
second call and compare the contents byte-for-byte. Retain the existing
second.Changed assertion while ensuring the second invocation produces exactly
the same file bytes as the first.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/components/mcp/inject_test.go`:
- Around line 499-505: Update the idempotency test around the second Inject call
to capture the ~/.claude.json bytes immediately after the first injection, then
read the file again after the second call and compare the contents
byte-for-byte. Retain the existing second.Changed assertion while ensuring the
second invocation produces exactly the same file bytes as the first.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b0fa078-5803-407e-909f-97dfaf9d8c51

📥 Commits

Reviewing files that changed from the base of the PR and between c5c1b07 and 20b2405.

📒 Files selected for processing (1)
  • internal/components/mcp/inject_test.go

@decode2

decode2 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Audited PR #1909 against linked Issue #1868 (status:approved). The retargeting to ~/.claude.json with strict 0600 permissions and safe JSON merging via claude.MergeUserConfig is clean, tests pass cleanly, and the diff remains within the 400-line review budget (360 lines). Ready for final review and merge.

@decode2 decode2 added the type:bug Bug fix label Jul 28, 2026
@Denver2828

Copy link
Copy Markdown
Contributor Author

Aplicada la asercion de idempotencia byte a byte del test (comentario fuera-de-diff de la ronda 3). Gracias @decode2 por la auditoria — queda listo para el review final.

@dnlrsls dnlrsls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Three issues need resolution before merge:

  1. internal/agents/claude/adapter.go:101-113 performs an uncoordinated read/merge/rename of ~/.claude.json. This PR makes both Context7 and Engram write that shared file, so concurrent runs can read the same base and last-writer-wins, dropping one registration. A concurrent Claude Code update can also be overwritten. Please serialize the complete operation or add file-identity conflict detection with retry.

  2. internal/components/filemerge/writer.go:48-52 returns before applying the requested mode when the merged bytes are unchanged. Consequently, claude.MergeUserConfig(..., 0600) can leave a byte-identical OAuth-bearing ~/.claude.json at 0644. Please enforce 0600 on the unchanged-content path and add coverage for an already-correct but loosely permissioned config.

  3. The current diff is 439 lines (+368/-71). .github/workflows/pr-check.yml counts raw additions plus deletions with a 400-line limit, and this PR has no size:exception; PR Validation will fail once the approval-gated workflows run. Please split the change or obtain the required exception.

Focused tests for the four affected packages, gofmt, and go vet pass locally. The hosted CI/PR Validation workflows have not run at this head because they are still action_required.

@Denver2828

Copy link
Copy Markdown
Contributor Author

@dnlrsls los tres puntos, resueltos o encaminados:

1. Concurrencia (aplicado): MergeUserConfig ahora hace deteccion de conflicto con retry — lee la base, mergea, re-lee, y si la base se movio (Claude Code o un run concurrente) reintenta desde cero, hasta 4 intentos antes de rendirse con error tipado. Un merge stale ya no puede pisar una registracion escrita en el medio. Elegi esta variante sobre el lock cross-process para no acuñar infraestructura de locking por archivo que deberia salir de la resolucion sistemica de #1809.

2. Modo en el camino no-op (aplicado): buen hallazgo — WriteFileAtomic retornaba antes de aplicar el modo cuando los bytes no cambiaban. MergeUserConfig ahora ejecuta os.Chmod(0600) incondicional despues del write, y hay test nuevo: primera inyeccion, chmod 0644 manual, segunda inyeccion no-op, asercion de que vuelve a 0600 (skip en Windows, donde Go reporta ACLs como 0666).

3. Tamano: el diff quedo en 499 (+428/−71) — los +139 sobre el presupuesto vienen integramente de las 4 rondas de review (helper compartido, preservacion de modos en uninstall, retry de concurrencia, y 5 tests de seguridad nuevos). Partirlo ahora separaria las dos mitades del mismo invariante (#1868 documenta ambas y el helper es el punto unico de enforcement de las dos). Pido formalmente el size:exception con esa justificacion; si los maintainers prefieren el split igual, separo la mitad de Engram en un PR encadenado.

Suites de los 4 paquetes afectados + gofmtcheck verdes en local (Windows 11 amd64, go1.26.2).

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/agents/claude/adapter.go`:
- Around line 106-112: Update the config-loading flow around readUserConfigBase
and the UnmarshalJSONObject validation so a nonexistent user config is treated
as an empty merge base and allows initialization. Preserve rejection of existing
empty or malformed files by performing JSON validation only when the base file
exists, using an existence flag or equivalent distinction from
readUserConfigBase, and apply the same behavior to the other referenced call
site.
- Around line 117-127: Protect the complete read-merge-write sequence containing
readUserConfigBase, the bytes.Equal retry check, and filemerge.WriteFileAtomic
with a shared cross-process lock, held through the write and released on every
return path. Ensure all writers use the same lock so no process can modify the
configuration between validation and atomic replacement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 860037ca-5ff6-4442-8ba6-ad8bf90e81d2

📥 Commits

Reviewing files that changed from the base of the PR and between 5373faa and 266d6c4.

📒 Files selected for processing (2)
  • internal/agents/claude/adapter.go
  • internal/components/mcp/inject_test.go

Comment on lines +106 to +112
raw, err := readUserConfigBase(configPath)
if err != nil {
return filemerge.WriteResult{}, configPath, err
}
if _, parseErr := filemerge.UnmarshalJSONObject(raw); parseErr != nil {
return filemerge.WriteResult{}, configPath, fmt.Errorf("refusing to modify %q: it holds the Claude Code session and could not be parsed as JSON: %w", configPath, parseErr)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Allow a missing user config to initialize the merge.

readUserConfigBase returns nil, nil for a nonexistent file, but UnmarshalJSONObject(nil) fails. First-time injection therefore aborts instead of creating ~/.claude.json—including TestInjectClaudeTightensModeOnNoOpRun.

Return an existence flag from readUserConfigBase, or only validate JSON when a base file exists; continue rejecting an existing empty or malformed file.

Also applies to: 141-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/agents/claude/adapter.go` around lines 106 - 112, Update the
config-loading flow around readUserConfigBase and the UnmarshalJSONObject
validation so a nonexistent user config is treated as an empty merge base and
allows initialization. Preserve rejection of existing empty or malformed files
by performing JSON validation only when the base file exists, using an existence
flag or equivalent distinction from readUserConfigBase, and apply the same
behavior to the other referenced call site.

Comment thread internal/agents/claude/adapter.go

@dnlrsls dnlrsls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This PR is not safe to merge yet. Three lifecycle defects need correction: (1) install verification and backup wiring still target the legacy Claude MCP paths instead of the new ~/.claude.json registry; (2) uninstall routes the user registry through cleanup that can delete the whole file when the last managed server is removed; and (3) the read/re-read/atomic-replace sequence can still lose a concurrent Claude or installer update between the final read and rename. Add focused regression coverage for all three. The PR is also 499 changed lines without size:exception, its body is missing required template sections, and the current head lacks the required hosted CI run. Please split below budget or obtain the explicit exception, repair the body, and rerun all required checks.

@Denver2828

Copy link
Copy Markdown
Contributor Author

Gracias por la review — los tres defectos eran reales. Fixes pusheados y verificados, uno por uno:

# Defecto Fix Commit Regresion
1 Verificacion post-install y backups apuntaban a los paths legacy de Claude componentPaths verifica ~/.claude.json para context7/engram de Claude; backupTargets snapshotea settings.json y los legacy ~/.claude/mcp/*.json que la inyeccion muta; el backup pre-upgrade incluye el registry 49e55da TestComponentPathsClaudeMCPUsesUserRegistry, TestBackupTargetsSnapshotClaudeMCPMutations, TestConfigPathsForBackup_CoversClaudeUserRegistry
2 Uninstall podia borrar ~/.claude.json entero al remover el ultimo server manejado Nueva operacion rewriteClaudeUserConfig: nunca borra el archivo (escribe el objeto vaciado), preserva el modo, y corre bajo el lock del punto 3 a99963f TestComponentOperationsClaudeNeverDeleteUserRegistry
3 Ventana entre el read final y el rename LockUserConfig: lock advisory cross-platform reutilizando reviewtransaction.AcquireAuthorityFileLock (el primitivo que el repo ya expone para authority stores adyacentes). MergeUserConfig y el uninstall lo toman, con espera acotada de 2s 1d0bc81 TestMergeUserConfigSerializesConcurrentWriters (8 escritores concurrentes, ninguna regisracion perdida), TestMergeUserConfigWaitsForLockHolder

Ademas, al correr la suite completa de internal/cli contra estos fixes aparecieron dos tests de integracion que asumian los paths viejos: el probe de idempotencia leia el legacy engram.json, y la verificacion en scope workspace tiene que apuntar al registry del dir escopado (el mismo que recibe el inyector), no al del home. Ambos corregidos en 654f507.

Una aclaracion honesta sobre el punto 3: el lock cierra la carrera entre escritores de gentle-ai (install, sync, upgrade, uninstall — todos cooperan). Claude Code no toma ningun lock propio sobre su archivo, asi que contra ese escritor la unica defensa posible sigue siendo el re-read/retry dentro de la seccion critica — la ventana queda minima pero no se puede eliminar sin cooperacion del otro proceso. Si conocen un mecanismo que Claude Code respete, lo sumo.

Sobre el proceso:

  • Body del PR: reparado con el template completo (issue linkeado, type, changes, test plan, checklist).
  • CI hosteado: el head nuevo (654f507) esta pusheado; los workflows del fork necesitan que alguien con permisos los dispare — cuando corran, cualquier rojo lo atiendo enseguida.
  • Presupuesto de lineas: aca hay una tension que prefiero mostrar con numeros antes que esquivar. El diff base era 398. Las 4 rondas de review — retry de concurrencia, chmod en runs no-op, y ahora estos 3 defectos con su cobertura — lo llevaron a ~860. Los dos pedidos vigentes (arreglar los 3 defectos con tests en este PR, y quedar bajo 400) no cierran jutnos. Veo dos salidas y ejecuto la que prefieran:
    1. size:exception con este scope — la label la aplican ustedes; el rationale es que +460 de las lineas nacieron de pedidos de review, no de scope creep.
    2. Split en 3 PRs encadenados — (a) helper compartido + lock + context7 (~400), (b) engram (~190, depende de a), (c) wiring de verificacion/backup/uninstall (~240, depende de a y b). Aviso el costo: entre el merge de (a) y el de (c), la verificacion post-install de Claude queda rota en main — exactamente el defecto 1 que me pidieron arreglar. Por eso mi preferencia es la opcion 1, pero la decision es de ustedes.

@dnlrsls dnlrsls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for addressing the earlier lifecycle findings. The verification/backup wiring and non-deleting uninstall path are now present, but the current head still has three merge-blocking correctness gaps:

  1. internal/cli/run.go:1342-1343,1747-1753,1953-1957: workspace-scoped Claude Engram routes and verifies <workspace>/.claude.json. Claude Code does not use that file for user MCP registration, so the command can report success while Engram remains unavailable. Keep workspace prompt delivery separate from the Claude user MCP registry, and merge/verify the supported registry location.

  2. internal/agents/claude/adapter.go:125-145: the final compare followed by atomic replacement still has a TOCTOU window for non-cooperating Claude Code writes. Claude can refresh OAuth/session state after the comparison and before rename, and the stale merged file then replaces it. Please use Claude's supported configuration command or an identity-bound compare-and-replace that refuses stale writes. The advisory lock only coordinates Gentle AI processes.

  3. internal/components/communitytool/tool.go:177-200 and internal/components/communitytool/codegraph_contract.go:88-92: the Claude-targeted CodeGraph installer writes the same ~/.claude.json registry without claude.LockUserConfig. Concurrent CodeGraph and Context7/Engram operations can still lose one registration. Route every Gentle AI writer through the same serialization boundary and add a concurrent regression test.

Two additional lifecycle gaps should be covered in the same correction: legacy Context7 cleanup at internal/components/mcp/inject.go:292-301 identifies ownership by server name alone and can delete a user-authored entry, and the 0600 guarantee is not enforced on Windows because os.Chmod does not establish a restrictive DACL while the regression test skips Windows.

Focused Claude/MCP/Engram/uninstall and CLI path/backup/idempotency tests pass. The PR still has 858 changed lines without size:exception, and only CodeRabbit is reported at this head, so the required hosted checks must also run before merge.

@Denver2828
Denver2828 force-pushed the fix/1868-claude-mcp-user-config branch from 654f507 to 1679991 Compare July 30, 2026 00:07
@Denver2828 Denver2828 changed the title fix(mcp): register Claude Code MCP servers in ~/.claude.json, the file it reads fix(mcp): register Claude Code Context7 MCP in ~/.claude.json Jul 30, 2026
@Denver2828
Denver2828 force-pushed the fix/1868-claude-mcp-user-config branch from 1679991 to 024e08f Compare July 30, 2026 00:12
@Denver2828

Denver2828 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@dnlrsls tomé la salida que venias señalando: split. Este PR quedo recortado al nucleo de context7.. rebaseado sobre main post-v2.2.2, 400 lineas exactas.. y el resto del trabajo queda mapeado en PRs chicos encadenados. El branch completo con todo lo revisado en estas 6 rondas existe y esta probado; nada se pierde, solo cambia el empaquetado.

Que queda en ESTE PR: registracion de context7 en ~/.claude.json (merge no destructivo: base corrupta aborta, retry ante base movida, 0600 siempre), limpieza best-effort del bloque inerte de settings.json, verificacion post-install apuntando al registry real, y uninstall que nunca borra el archivo aunque quede vacio.

Roadmap de lo demas, incluyendo tus 3 puntos de la ultima review:

Pieza Contenido Estado
PR B.. engram registracion de engram en el registry + rescate del command del legacy ~/.claude/mcp/engram.json + su uninstall y verificacion listo en mi fork, sale al mergear este
PR C.. serializacion LockUserConfig (lock advisory reutilizando AcquireAuthorityFileLock) tomado por merge, uninstall y el installer de CodeGraph (tu punto 3), + test de escritores concurentes + snapshot de backup de settings/legacy (#1794) listo en mi fork, depende de B
Issue nuevo.. workspace scope tu punto 1: el registry escopado a workspace no es un archivo que Claude Code lea; propongo registrar siempre en el user registry del home y dejar el workspace solo para prompts.. lo escribo como issue para acordar el diseño antes de codearlo lo abro en estos dias
Issue nuevo.. TOCTOU contra Claude Code tu punto 2: con primitivas de filesystem no hay compare-and-replace atomico contra un proceso que no coopera; delegar en claude mcp add cuando el binario esta disponible es viable pero es un cambio de diseño (dependencia runtime del CLI) que merece decision de maintainers lo abro en estos dias
Issue nuevo.. 0600 en Windows tu punto extra: os.Chmod no establece DACL restrictivo en NTFS; documentar la limitacion o implementar ACLs via golang.org/x/sys/windows lo abro en estos dias

Sobre el ownership del cleanup (tu otro punto): tenias razon en settings.json.. el bloque se removia con chequear solo el nombre context7. Arreglado en este PR: isManagedSettingsContext7Entry verifica la forma managed (command npx + args de context7-mcp) antes de tocar nada, con caso de regresion para una entrada user-authored. El removedor del archivo legacy ya comparaba contenido (removeManagedContext7File), ese quedo como estaba.

Con esto el PR cumple el budget sin exception. Cuando puedan, disparen los workflows del fork para este head.. cualquier rojo lo atiendo enseguida.

@Denver2828

Denver2828 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Los tres issues de diseño del roadmap ya estan abiertos:

Con esto, cada punto de la ultima review tiene su casa: los arreglados aca, los que van en los PRs B y C de la cadena, y los de diseño en sus issues.

Claude Code only reads user-scope MCP servers from ~/.claude.json. Register
Context7 there with a non-destructive merge (corrupt base aborts, 0600
enforced, stale merges re-read and retry), clean the inert settings.json
block best-effort, verify the registry post-install, and never delete the
user registry on uninstall even when it empties.

Closes Gentleman-Programming#1868
@Alan-TheGentleman
Alan-TheGentleman force-pushed the fix/1868-claude-mcp-user-config branch from 024e08f to e5220fb Compare July 30, 2026 08:56
@Alan-TheGentleman

Copy link
Copy Markdown
Contributor

Maintainer rebase: I rebased this branch onto current main to resolve the conflict with #1617 (scoped Context7 injection). Two adjustments were needed to keep both fixes working together:

Full internal/cli, mcp, uninstall, and claude test suites pass locally. Thanks for the solid merge-safety work on MergeUserConfig — the retry-on-moving-base design held up nicely through the rebase.

@Alan-TheGentleman Alan-TheGentleman added the size:exception Maintainer-approved exception for PRs above the 400 changed-line review budget label Jul 30, 2026
@Denver2828

Denver2828 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Gracias por el rebase, Alan.. lo verifique de mi lado y esta todo verde.

Verificacion en Windows 11 (mi maquina, el entorno donde nacio el bug):

Check Resultado
go build ./... limpio
internal/components/mcp ok
internal/components/uninstall ok
internal/agents/claude ok
TestComponentPathsContext7ClaudeUsesUserRegistry (mio) PASS
TestComponentPathsContext7ClaudeRespectsWorkspaceScope (de #1617) PASS
go run ./internal/gofmtcheck limpio

El split que elegiste.. targetDir == homeDir manda al registry y workspace conserva el merge escopado de #1617.. es mas prolijo que lo que yo tenia: deja la decision en un solo lugar en vez de repatirla entre el inyector y la verificacion.

Una sola cosa que dejo anotada sin re-litigarla aca, porque le abri issue aparte: en scope workspace la registracion sigue quedando en un archivo que Claude Code no lee para MCP de user scope.. es el #2007. Si hay un motivo por el que ese path si sirve, cerramos el issue y listo; y si no, se discute ahi con calma sin frenar este PR.

Dato para tu decision: el rebase dejo el diff en 487 lineas (venia en 400 exactas despues del split). Las ~87 extra son la resolucion del confilcto con #1617, no scope nuevo mio. Decime si preferis que intente recortarlo, o si con tu propio rebase el tema del presupuesto queda saldado.

Lo que sigue de mi lado, ya construido y probado, para abrir cuando este mergee: engram al registry (lo adapto a la firma nueva de Inject), y el lock advisory que serializa a todos los escritores de gentle-ai de ese archivo.. incluido el installer de CodeGraph, que hoy escribe el mismo registry sin coordinarse con nadie.

@Alan-TheGentleman
Alan-TheGentleman merged commit 3603f22 into Gentleman-Programming:main Jul 30, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:exception Maintainer-approved exception for PRs above the 400 changed-line review budget type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(mcp): Claude Code MCP config written to ~/.claude/settings.json, a key Claude Code never reads

4 participants