Massu 1.8.0: MCP permission seeding suite
Closes two structural classes in one release.
Class 1 — cold-install permission dialog dance
Every fresh adopter hit per-tool permission dialogs on each of the 73+ mcpmassu MCP tool calls until they hand-curated .claude/settings.local.json. The new SSOT packages/core/src/permissions.ts exports MASSU_PERMISSION_ENTRIES = ['mcpmassu'] — a single canonical glob covering all current and future tools.
installPermissions is now called inside runInstallCommands / installAll via runWithManifest, so the seed lands on every npx massu install-commands. An escape hatch --skip-permissions flag is available for enterprise-managed allowlists.
Class 2 — merge-replacement trap (undocumented Claude Code behavior)
Empirically observed 2026-05-14 PM: a project-local permissions object without defaultMode silently STRIPS the user-global defaultMode during settings merge. The merge unit appears to be the entire permissions object's top-level keys collectively, not individual keys within it — undocumented at code.claude.com/docs/en/permissions.
The structural fix: mergedPermissionState(global, local, canonical) reads ~/.claude/settings.json, computes the FULL merged permissions block (allow ∪ canonical entries; defaultMode = local override OR global OR omit; deny/ask preserved), atomic-writes the complete block, and fail-loud-asserts post-write that defaultMode survived. Throws InstallPermissionsAssertionError if not.
New CLI cluster: massu permissions <sub>
Three subcommands mirror the existing config <sub> dispatch pattern:
| Subcommand | Effect | Exit codes |
|---|---|---|
install | Seeds canonical entries + propagates global defaultMode (idempotent, kept-because-edited preservation) | 0 |
verify | Read-only check that all canonical entries are present | 0 if clean / 1 with missing: <entry> per missing |
check-drift | Extended diagnostic surfacing 4 drift kinds | 0 / 1 / 2 / 3 / 4 (severity-mapped) |
Exit code matrix for check-drift (highest severity wins):
- 0 = clean
- 1 =
missing-allow(canonical entries missing) - 2 =
invalid-default-mode(defaultMode requires--permission-modelaunch flag) - 3 =
unknown-key(undocumented top-level setting) - 4 =
strips-global-defaultmode(project-local would strip global value)
Shared atomic IO
New packages/core/src/lib/settings-local.ts SSOT for .claude/settings.local.json AND ~/.claude/settings.json reads. Used by install-commands manifest writes + init.ts:installHooks + doctor.ts:106,241 per CR-9 consolidation. Closes a pre-existing non-atomic-write bug at init.ts:1137 (was writeFileSync — vulnerable to SIGINT-between-truncate-and-write leaving a corrupt settings.local.json).
Ship state
Production deploy at https://massu.ai/ HTTP 200 with [1.8.0] changelog entry live. 39 new tests pass (19 PERM-DRIFT + 7 SLOC + 10 VPC + 3 ICP). Pattern scanner 16/16 PASS. Plan converged 13→5→0 across 3 audit iterations.
See plan-1.8.0-mcp-permission-seeding and [1.8.0] in CHANGELOG.md.