Repository navigation
New-PC Claude Code user profile: native claude-code pin, guard, agents, user-scope MCP, settings apply - #139
Conversation
…d/agents/MCP assets, apply tooling Pin claude-code to the native 2.1.280 self-installing binary on both Linux and macOS (dropping the npm wrapper + platform_dependency design for macOS), ship this host's verified effort-default-guard.py hook, the five project agents, and the ai-memory/jcodemunch/serena user-scope MCP template as reusable adoption assets. Add tools/adoption/apply_claude_settings.py (deep-merging settings template apply) and tools/adoption/install_claude_profile.py (idempotent guard/agents/MCP install), wired into both bootstrap scripts via --configure-claude-user-profile. Update the settings template to match the live host (Opus 5.5 modelSettings, ultracode, workflow env, guard hooks) and record four already-installed catalog winners (markitdown, tavily-cli, orx, agent-browser) in the Linux pins, each independently re-verified against its publisher. Add a dated 2026-09-23 community disposition review and a decision record for this round's evidence and overturn conditions. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…, event-wide hook dedup
- install_native names the launcher after the pin's `bin` (claude-code -> claude),
so the --configure-claude-user-profile step finds $bin_dir/claude.
- markitdown and tavily-cli install as uv tools (own venv), matching this host's receipts;
VALID_KINDS no longer lists an uninvented kind.
- install_claude_profile renders ${HOME}/${ECO_ROOT} (--eco-root), orders
`mcp add --scope user <name> -e K=V -- <command> ...`, and leaves a differing
same-named server unchanged unless --replace-mcp. Verified against claude 2.1.280
under a temporary CLAUDE_CONFIG_DIR: registers all three, second run skips all three.
- apply_claude_settings de-duplicates hooks across the whole event by shell words,
keeps absent and "" matchers distinct, deep-merges every nested object with list
union, and never overwrites an earlier backup. Applying the rendered template to
this host's settings is a no-op.
- Guard tests skip when a managed-settings file exists and cover heal-once and an
explicit lower session effort; doc rows cite in-repo sources; darwin hash tail fixed.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…h after the rebase Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…; exact MCP matching - install_native skips its launcher when bin_dir is ~/.local/bin (the native installer's own symlink into ~/.local/share/claude/versions) and removes an existing launcher path before writing, so the redirect never writes through a symlink. Behavioral test runs each script's install_native with a stub installer; the previous code overwrote the versioned binary in that case. - The printed follow-up command carries --eco-root; default_eco_root follows --home. - MCP matching parses `claude mcp get` fields (Type, Command, Args in order, URL, Environment names) from recorded 2.1.280 output, and `mcp get` runs from an empty temporary directory so project/local entries cannot shadow the user one. Real CLI under a temporary CLAUDE_CONFIG_DIR: register, skip, differs (left unchanged), --replace-mcp dry run shows the remove, re-register. - Backups skip dangling-symlink names; malformed hook groups no longer crash apply. - evidence.json keeps the canonical writer's ASCII escapes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ne, clean missing-claude failure The printed follow-up command also names --claude-bin. Tests cover a Headers line named URL, a managed-scope server (no remove/add) and a missing binary. Real CLI re-check under the temporary CLAUDE_CONFIG_DIR: all three skipped. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
https://github.com/seathatflowsinourveins/native-agent-stack/blob/74972f8145d89e94090207df91ac1f755df44797/adoption/bootstrap-linux.sh#L330-L334
Verify uv-tool downloads against the committed hashes
When a uv-tool pin such as the newly added markitdown or tavily-cli is selected, this path ignores both the pinned artifact URL and its SHA-256 and asks uv to resolve the package from the index. A changed or differently resolved artifact can therefore be installed successfully despite the bootstrap's stated fail-closed SHA-256 guarantee; install from the fetched, verified artifact or otherwise bind uv to the committed digest.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "jcodemunch": { | ||
| "type": "stdio", | ||
| "command": "${ECO_ROOT}/bin/jcodemunch-mcp", |
There was a problem hiding this comment.
Install MCP executables before registering them
On a fresh foundation-cpu bootstrap using --configure-claude-user-profile, this registers jcodemunch-mcp and serena-context paths that do not exist: neither executable has an entry in either platform pin file, and no profile in adoption/manifest.json installs either component. The registration itself can therefore succeed while two advertised MCP servers fail whenever Claude launches them; install these prerequisites or omit their registrations until their native recipes have run.
AGENTS.md reference: AGENTS.md:L47-L50
Useful? React with 👍 / 👎.
| if "mcp" in steps: | ||
| eco_root = Path(args.eco_root) if args.eco_root else default_eco_root() | ||
| install_mcp_servers(args.claude_bin, args.dry_run, home, eco_root, args.replace_mcp) |
There was a problem hiding this comment.
Honor the target home during MCP setup
When --home differs from the caller's actual home, the guard and agents are copied under the requested target, but the MCP subprocesses still inherit the caller's environment and default_eco_root() also derives its default from Path.home(). Thus the same invocation mutates the caller's Claude configuration and renders commands against the caller's ecosystem prefix instead of the advertised target home; derive the default prefix from home and run Claude with the target configuration environment.
AGENTS.md reference: AGENTS.md:L61-L61
Useful? React with 👍 / 👎.
| WANT = "xhigh" | ||
| RANK = {"low": 0, "medium": 1, "high": 2, "xhigh": 3, "max": 4} | ||
| USER = os.path.expanduser("~/.claude/settings.json") | ||
| MANAGED = "/etc/claude-code/managed-settings.json" |
There was a problem hiding this comment.
Read managed settings from the macOS location
On the newly supported macOS profile, the guard only checks Linux's /etc/claude-code/managed-settings.json and therefore misses macOS managed policy. A managed per-model effort or maxEffortLevel can be ignored, causing incorrect SessionStart advice and a misleading user-level self-heal after SessionEnd; select Claude Code's platform-specific managed-settings path before resolving or saving effort.
AGENTS.md reference: AGENTS.md:L61-L61
Useful? React with 👍 / 👎.
| parser.add_argument("--template", default=str(DEFAULT_TEMPLATE), | ||
| help="Rendered claude.settings.template.json to merge in " | ||
| "(render it first with tools/adoption/render_config.py --out; " | ||
| "this tool does not substitute ${...} placeholders itself)") |
There was a problem hiding this comment.
Reject the unrendered default settings template
Invoking this tool without --template silently selects the repository's raw template even though the adjacent help text says it must be rendered first. The merge then writes literal ${ECO_ROOT}, ${HOME}, and ${HOST_PATH} values and keeps escaped template syntax such as $$schema, leaving PATH and hook commands unusable in the live Claude configuration; require an explicitly rendered file or reject unresolved placeholders before writing.
AGENTS.md reference: AGENTS.md:L61-L61
Useful? React with 👍 / 👎.
| dest_dir.mkdir(parents=True, exist_ok=True) | ||
| shutil.copy2(GUARD_SRC, dest) | ||
| dest.chmod(0o755) |
There was a problem hiding this comment.
Refuse symlinked profile destinations
If an existing guard destination is a symlink whose target differs from the catalog copy, shutil.copy2 follows the link and overwrites that external target, after which chmod also changes its mode to 0755; the agent-copy loop has the same behavior. This can corrupt a user- or configuration-manager-owned file outside ~/.claude/hooks or ~/.claude/agents, so reject symlink destinations or atomically replace the link itself rather than following it.
AGENTS.md reference: AGENTS.md:L61-L61
Useful? React with 👍 / 👎.
| os.chmod(tmp, os.stat(USER).st_mode & 0o777) | ||
| except OSError: | ||
| pass | ||
| os.replace(tmp, USER) |
There was a problem hiding this comment.
Serialize SessionEnd settings updates
When two Claude sessions end concurrently, or a user edits settings while this hook runs, each process can load the same old JSON and then replace the entire settings file with its own stale copy. Atomic replacement prevents a partial file but not this lost-update race, so one model's self-heal can erase another model entry or unrelated settings changes made after line 117; lock the read-modify-write cycle or revalidate and merge immediately before replacement.
AGENTS.md reference: AGENTS.md:L6-L8
Useful? React with 👍 / 👎.
| if command_or_url not in existing_text: | ||
| return False | ||
| for arg in args: | ||
| if arg and arg not in existing_text: | ||
| return False |
There was a problem hiding this comment.
Compare structured MCP configurations
For an existing stdio registration, matching each desired command and argument as an arbitrary substring accepts materially different configurations—for example, command /e/bin/jcodemunch-mcp-evil matches /e/bin/jcodemunch-mcp, and extra or reordered arguments are never rejected. Such an entry is reported as matching and skipped, leaving the intended user-scope server undiscovered; parse the returned fields and compare the exact command and argument vector.
AGENTS.md reference: AGENTS.md:L61-L61
Useful? React with 👍 / 👎.
| { | ||
| printf '#!/usr/bin/env bash\n' | ||
| printf '# Native auto-updating launcher (installed by %s install); the ecosystem no longer pins a snapshot.\n' "$id" | ||
| # shellcheck disable=SC2016 | ||
| printf 'exec "$HOME/.local/bin/%s" "$@"\n' "$bin_name" | ||
| } > "$bin_dir/$bin_name" |
There was a problem hiding this comment.
Avoid overwriting the native launcher with a recursive wrapper
If a caller chooses the otherwise accepted ECO_INSTALL_ROOT=$HOME/.local, then bin_dir/claude is the same path as the native installer's $HOME/.local/bin/claude. This write replaces the real launcher with a wrapper that execs itself forever; the later version-report loop masks the resulting nonzero --version, so the bootstrap can still print completion with Claude unusable. Both platform scripts share this implementation, so reject overlapping roots or avoid creating a wrapper when both paths resolve to the same file.
AGENTS.md reference: AGENTS.md:L61-L61
Useful? React with 👍 / 👎.
| model: sonnet | ||
| effort: medium | ||
| isolation: worktree |
There was a problem hiding this comment.
Bound every installed worker's turns
The installed isolated-builder, evidence-reviewer, and semantic-evidence-reviewer omit maxTurns, unlike the bounded source-scout and blind-judge. When one of these roles enters an unproductive tool or review loop, the profile's concurrency and spawn-depth limits do not cap that worker's sequential turns, leaving execution and usage unbounded; add task-appropriate turn ceilings to each shipped role.
AGENTS.md reference: AGENTS.md:L6-L8
Useful? React with 👍 / 👎.
…tions to their host; record sparse-VHD, gitleaks-order and WSL lessons (#230) * Run Serena's own script from the MCP templates; attribute pin observations to their host; record sparse-VHD, gitleaks-order and WSL lessons - adoption/mcp/claude-user.json and adoption/templates/project.codex.config.template.toml launched serena through ${ECO_ROOT}/bin/serena-context, a wrapper that existed only on the 2026-09-23 recording host (it set SERENA_HOME); nothing in this repository creates it. Both now run ${ECO_ROOT}/bin/serena with the same arguments: the console script `uv tool install` links there for serena-agent at the stack pin c6fbd1c5 with the bootstrap's uv-tool layout. The template comment gives that command and says the ai-memory URL (127.0.0.1:49374, the repository default) must match the host's own ai-memory unit. - tests/test_install_claude_profile.py follows the template, keeps the 2026-09-23 `claude mcp get` recording, adds a 2026-09-25 recording of the new registration (claude 2.1.282, temporary CLAUDE_CONFIG_DIR) and checks that the older registration is reported as differing. - adoption/pins-linux-x86_64.json: the markitdown, tavily-cli, orx and agent-browser install notes attribute their "installed" observation to the host that recorded PR #139 on 2026-09-23. Versions, URLs and hashes are unchanged. - docs/next-host-stages.md and docs/new-workstation-runtime-profile-20260922.md: dated notes that sparseVhd is disabled upstream (enabling it needs --set-sparse true --allow-unsafe), how to reclaim VHD space instead, and the workstation's live .wslconfig, which differs from the projection. - docs/secret-storage.md step 5: install and verify gitleaks 8.30.1 and the guarded launcher (5a) before enabling the hook (5b). - adoption/platforms/linux-wsl2.md: Windows-side command and listener lessons, each with its upstream source. - docs/acceptance-evidence-policy.md: discriminating controls. - manifests/evidence.json: re-registers the eight hash-listed files. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * Disclose the install_note-only pins change on the two pages that cite the pin file tests.test_adoption_docs_consistency requires every page that mentions adoption/pins-linux-x86_64.json to say what changed after the pinned release. adoption/bootstrap.md and adoption/platforms/macos-arm64.md now say the change is install_note text only; versions, URLs and hashes are unchanged. manifests/evidence.json re-registers both files. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * Install the MCP servers the template names; safe re-registration; hook check with a planted-key control Independent-review repair round for #230. - adoption/bootstrap.md step 4a installs serena (git pin c6fbd1c5) and jcodemunch-mcp 1.108.319 as uv tools into ${ECO_ROOT}/python-tools and ${ECO_ROOT}/bin before the MCP registration. It names the MCP template and the project Codex template among the files changed after v2026.09.24.1 (the tag's serena entry runs a serena-context wrapper nothing installs), and gives the re-registration sequence: remove the servers that should change, then install_claude_profile.py --only mcp without --replace-mcp. That flag re-registers every differing server. An ai-memory unit on another port needs `claude mcp remove ai-memory -s user` before `claude mcp add`, because `claude mcp add` refuses an existing name. - adoption/mcp/claude-user.json comment and adoption/platforms/linux-wsl2.md say the same; the port rule applies to the registration, not the template. adoption/platforms/macos-arm64.md step 3 names the template change. - recipes/README.md serena row points at the installed command and bootstrap step 4a, and keeps uvx for one-off runs. - docs/secret-storage.md: 5a names $ECO_INSTALL_ROOT/tools/gitleaks-8.30.1/ as the extraction target with checksum-verified commands, and says how `gitleaks` resolves to gitleaks-guarded-macos on macOS and what passes. 5b checks core.hooksPath and runs `git hook run pre-commit` in a throwaway worktree, clean (exit 0) and with a planted generated AWS-style key (exit 1). The hook is absent at v2026.09.24.1. - adoption/tools/README.md macOS install adds the `gitleaks` link and its pass criterion. - docs/acceptance-evidence-policy.md: the failing control is recorded with the passing run, the rule governs new claims, and the planted-key check is the example. - docs/next-host-stages.md: Optimize-VHD -Mode Full needs Mount-VHD -ReadOnly. docs/new-workstation-runtime-profile-20260922.md compares each live value with its projection. - manifests/evidence.json re-registers the nine hash-listed files. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Scout <scout@local> Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
What
Reproduces this host's current best-practice Claude Code profile on a new Linux/WSL or macOS PC from the catalog:
claude-code2.1.280 on both platforms (the binary's sha256 matches the release manifest; it auto-updates on the latest channel; the launcher is named by the pin'sbin). The winnersmarkitdownandtavily-cliinstall as uv tools;orxfrom a tarball;agent-browserfrom npm (Linux).adoption/hooks/claude/effort-default-guard.py(sha256-checked), five lean agents inadoption/agents/claude/, and user-scope MCP templates inadoption/mcp/claude-user.json(ai-memory, jCodeMunch, Serena).tools/adoption/install_claude_profile.py(guard, agents, MCP): renders${HOME}and${ECO_ROOT}, runsmcp add --scope user <name> -e … -- <cmd>, and leaves a differing server unchanged unless--replace-mcp.tools/adoption/apply_claude_settings.py: deep merge; hook de-duplication across each event by shell words; backups never overwrite an earlier one.--configure-claude-user-profile.opus[1m],modelSettings.claude-opus-5-5.effortLevel=xhigh, ultracode,showThinkingSummaries,cleanupPeriodDays3650,autoContinueAtUsageLimit, and the guard hooks.docs/decisions/2026-09-23-claude-user-profile.md, plus dated community-disposition rows. New-host pages mark every install input changed or added afterv2026.09.23.Evidence
e6ca4dd6/74972f81:-eargument ordersystem-packagekindclaude2.1.280 in a temporaryCLAUDE_CONFIG_DIRregistered all three servers with rendered paths; a second run skipped all three. The host's~/.claude.jsonMCP entries were unchanged.validate.py(2,470 hashed files),validate_catalogs.py,evidence_manifest.py --check,new_host_grand_list.py --check.stop_blocks_entryerrors among them). This branch adds no new failures.Not verified
.sigis unverified.🤖 Generated with Claude Code