# C3 spike findings: `--setting-sources` interaction + overlay file placement _Task: C3 in [system-prompt-profiles-tasks.md](system-prompt-profiles-tasks.md). Feeds B2._ ## Method note (important correction mid-spike) Initial attempts asked the model to self-report whether ORCHESTRATION.md/skills were injected ("ORCH=yes/no SKILL=yes/no"). This was **unreliable** — a default run that the transcript later proved *did* receive the orchestration injection had the model answer `ORCH=no`. Model self-report is not a valid signal for this spike. Switched to a deterministic signal: the session transcript JSONL at `~/.claude/projects//.jsonl` records hook execution as `attachment.type == "hook_additional_context"` entries (one per SessionStart hook that returned `additionalContext`) and the full injected skill list as `attachment.type == "skill_listing"`. Both are populated *before* the model ever sees the turn, so they're a ground-truth substitute for `/context`. ## Spike (a): `--setting-sources` vs symlinked local plugins + absolute-path hooks 4 headless haiku runs from an empty scratch dir (`/tmp/claude-1000/.../scratchpad/c3spike`), `claude -p --model haiku --output-format json "reply OK"`, varying `--setting-sources`. For each, inspected the resulting transcript JSONL for hook-injected additionalContext count and whether plugin-specific skills (`os-adr`, `os-vault`) appear in the injected skill listing (built-in skills like `autoresearch`/`caveman` appear regardless — only plugin skill presence is diagnostic). | `--setting-sources` value | additionalContext entries (hooks fired) | os-adr/os-vault skills present | |---|---|---| | (no flag, default) | 1 (confirmed in first exploratory run) | yes | | `""` (none) | 0 | no | | `user` | 1 | yes | | `project,local` | 0 | no | Interpretation: - Hooks in this setup are wired **only** in `~/.claude/settings.json` (a **user**-scope settings file), by absolute path into `cc-os/`. They fire if and only if the `user` settings source is included. `--setting-sources ""` and `--setting-sources project,local` both silently drop them — no error, no warning, just no `additionalContext`. This is a real footgun for a future `cyolo` profile wrapper: any profile that passes `--setting-sources` explicitly must include `user` or it silently loses ORCHESTRATION.md injection, os-status checks, os-vault hooks, and every other hook in the plugin family. - Plugin skill registration (the `os-adr:find` style slash-command/skill listing) tracks the same axis: present with `user` included, absent without it. This makes sense — `enabledPlugins` lives in the user-scope `~/.claude/settings.json`, so a `--setting-sources` value that excludes `user` also excludes plugin enablement, not just hooks. - Symlinks themselves are a non-issue: once the `user` settings source is included, the symlinked plugin (`~/.claude/plugins/os-adr -> cc-os/plugins/os-adr`) resolves and loads identically to the default case — no special-casing needed for the symlink or for the hooks' absolute paths into `cc-os/`. The failure mode is entirely about which settings **source** is included, not about symlinks or path resolution. **Verdict (a):** `--setting-sources` interacts cleanly with symlinked plugins and absolute-path hooks in the sense that nothing breaks or errors — but it is an all-or- nothing gate: excluding `user` from `--setting-sources` silently disables every cc-os plugin (hooks AND skills) with no error surfaced. Any profile wrapper must always include `user` in `--setting-sources` (or simply not pass the flag) to keep the plugin family alive; only `project`/`local` inclusion should be varied per profile. ## Spike (b): where should per-profile settings overlay files live? Read `bin/refresh-plugins` (Ruby). It touches exactly these paths, all read-only except for the plugin cache itself: - Reads `~/.claude/plugins/known_marketplaces.json` (marketplace list/config) - Reads `~/.claude/plugins/installed_plugins.json` (which plugins are installed per marketplace) - For each installed plugin under a `directory`-source marketplace, shells out to `claude plugin uninstall @` / `claude plugin install @` — this rewrites `~/.claude/plugins/cache// //` (via Claude Code's own plugin manager, not directly) and updates `installed_plugins.json`'s install record for that plugin. - Verifies via `diff -rq` between the cache dir and the plugin's source dir under `source_path` (from marketplace config, e.g. `~/dev/cc-os/plugins` or `~/dev/cc-plugins`). It never touches `~/.claude/settings.json`, never touches any file under a `~/.claude/profiles/` style directory, and never touches arbitrary `--settings ` overlays — its blast radius is strictly `known_marketplaces.json`, `installed_plugins.json`, and the `plugins/cache///` tree. **Recommendation:** put per-profile settings overlay JSONs at `~/.claude/profiles/*.json` (e.g. `~/.claude/profiles/cc-os-design.json`, `client-dev.json`, `brainstorm.json`), passed via `claude --settings ~/.claude/profiles/.json`. This path is structurally disjoint from everything `refresh-plugins` reads or writes (`plugins/cache/`, `plugins/known_marketplaces.json`, `plugins/installed_plugins.json`) — there is no plausible future edit to `refresh-plugins` that would need to reach into `~/.claude/profiles/`, since that script's whole job is cache-vs-source verification for marketplace-installed plugins, not settings resolution. Interaction with `enabledPlugins`: `--settings ` is merged with (not a full replacement for) the resolved settings from the sources selected by `--setting-sources` (per spike (a), keep `user` in the source list so the base `~/.claude/settings.json` `enabledPlugins`/hooks/env still apply). The profile overlay should therefore only need to express a *diff* — an `enabledPlugins` map with the profile's unwanted plugins set to `false` (or, if this repo prefers positive lists, the full set with only the wanted ones `true`) — rather than duplicating the whole settings file. This diff-only overlay is exactly what B2 should generate per profile. One caution to carry into B2: verify at build time whether CC deep- or shallow-merges the `enabledPlugins` object across `--settings` sources (not tested in this spike) — either way, a plugin disabled via the overlay's `enabledPlugins: {"name": false}` does not require touching `known_marketplaces.json` or `installed_plugins.json`, since `enabledPlugins` toggles are independent of the marketplace/install-record identity those files hold (the files `refresh-plugins` owns). **Verdict (b):** `~/.claude/profiles/*.json` (passed via `--settings`) is safe — it lives entirely outside the three paths `refresh-plugins` reads/writes/rewrites (`known_marketplaces.json`, `installed_plugins.json`, `plugins/cache/`), and `enabledPlugins` toggles in an overlay are orthogonal to the marketplace/cache identity `refresh-plugins` verifies, so cache refreshes and profile overlays can never clobber each other.