feat(plugins): wire extraKnownMarketplaces + enabledPlugins via container.json#2365
Open
yaniv-golan wants to merge 2 commits into
Open
feat(plugins): wire extraKnownMarketplaces + enabledPlugins via container.json#2365yaniv-golan wants to merge 2 commits into
yaniv-golan wants to merge 2 commits into
Conversation
…iner.json Per-group plugin support via the SDK's settings-driven install path. Operators declare marketplaces + enabled plugins in groups/<folder>/container.json:plugins; group-init mirrors them into per-group settings.json on every spawn (additive merge), and container-runner sets CLAUDE_CODE_SYNC_PLUGIN_INSTALL=1 + CLAUDE_CODE_REMOTE=1 when plugins are declared. CLAUDE_CODE_REMOTE is mandatory — without it, github source URLs default to SSH and bypass OneCLI's HTTPS proxy entirely. Also adds the container.json concurrency primitive that subsequent plugin/marketplace operator skills will need: writeContainerConfig is now atomic (write-then-rename), updateContainerConfig acquires an advisory file lock with 60s stale TTL. Existing read-modify-write callers (self-mod apply.ts handlers, container-runner's ensureRuntimeFields and per-agent-group imageTag persistence) all routed through the locked path. Container side: claude provider's translateEvents() now handles SDK plugin_install system messages — `failed` status emits a new plugin_install_failed provider event so install errors surface in host logs; started/installed/completed are activity-only. New event variant added to ProviderEvent in types.ts; poll-loop logs it. Tests: 12 new vitest cases — atomic writes, concurrent writers (10 parallel updaters), lock release on throw, defensive parsing of stale-format settings.json (malformed JSON, non-object top level, missing blocks), plugins config round-trip, additive merge, key-collision precedence, idempotency. vitest.config.ts: exclude container/ so vitest 4's auto-workspace discovery doesn't pick up the bun:test files in agent-runner — drive-by fix needed for the new tests to run from the host root. Pre-PR empirical test report (Q1, Q2, Q4, Q5, Q9 all confirmed positively) lives at docs/internal/plugin-install-empirical-test.md (gitignored, fork-internal). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OneCLI's applyContainerConfig() injects NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, and DENO_CERT for various language runtimes — but not GIT_SSL_CAINFO. Without it, git's HTTPS verification falls back to the system CA bundle, doesn't trust the OneCLI MITM cert, and fails every clone through the gateway with: fatal: unable to access 'https://github.com/...': server certificate verification failed. CAfile: none Symptom (caught during PR 1 end-to-end test): SDK marketplace clone fired but failed even for public github repos because HTTPS through the proxy couldn't verify the cert; SDK then fell back to SSH which also failed (no agent forwarding in container) and surfaced as a plugin_install:failed event. Fix: post-process the args after applyContainerConfig — find the NODE_EXTRA_CA_CERTS=<path> entry it just added, mirror the same path into GIT_SSL_CAINFO. Same path, both env vars, git happy. Manual public-repo HTTPS clone inside the container now succeeds. SDK plugin install path will work for public repos without further config; private repos still need the OneCLI vault entry from /setup-private-plugins. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced May 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add per-group plugin support via the SDK's settings-driven install path. Operators declare marketplaces + enabled plugins in
groups/<folder>/container.json:plugins; the host mirrors them into per-groupsettings.json(extraKnownMarketplaces/enabledPlugins) on every spawn, and the container env getsCLAUDE_CODE_SYNC_PLUGIN_INSTALL=1+CLAUDE_CODE_REMOTE=1(gated on plugins being non-empty).Also lays the foundation for safe concurrent writes to
container.json(needed by the per-group operator skills in a follow-up PR).What's new
Host:
ContainerConfig.pluginsfield with the SDK's typedextraKnownMarketplacessource schema (8 variants) andenabledPluginsvalue union (boolean | string[] | object).updateContainerConfig(folder, mutate)async helper. Atomic write-then-rename inwriteContainerConfig. Advisory file lock (60s stale TTL, jittered polling). ExistingapplyInstallPackages/applyAddMcpServercallers routed through the locked path;ensureRuntimeFieldsand per-group imageTag write also locked.ensurePluginsConfig()in group-init mirrorscontainer.json:pluginsintosettings.jsonon every spawn. Defensive against malformed/non-object/stale-format settings.json. Additive merge (entries already present in settings.json that aren't in container.json are preserved).CLAUDE_CODE_SYNC_PLUGIN_INSTALL=1+CLAUDE_CODE_REMOTE=1when plugins block is non-empty.CLAUDE_CODE_REMOTEis mandatory — without it, github source URLs default to SSH and bypass the OneCLI gateway entirely.GIT_SSL_CAINFOis now set to mirrorNODE_EXTRA_CA_CERTS. OneCLI'sapplyContainerConfig()injects the latter for various language runtimes but not for git, so the SDK's marketplace clones (which shell out togit clone) failed with "server certificate verification failed" through the gateway. With the new env, git trusts the OneCLI MITM cert and clones complete normally.Container:
plugin_install_failedvariant onProviderEvent.claude.ts:translateEvents()handles SDKplugin_installsystem messages —failedstatus emits the new event, others are activity-only.poll-loop.ts:handleEventlogsplugin_install_failedevents for operator visibility.Tests
src/container-config.test.tsandsrc/group-init.test.ts:.tmp.*leftovers after rename)**/container/**(which usesbun:test) doesn't get picked up by host vitest under v4's auto-workspace discovery.How operators use it
Without follow-up operator skills, an operator can already use this PR by hand-editing
groups/<folder>/container.json:{ "plugins": { "marketplaces": { "anthropic": { "source": { "source": "github", "repo": "anthropics/skills", "ref": "main" } } }, "enabled": { "doc-coauthoring@anthropic": true } } }Restart the group; the SDK installs the marketplace + plugins at next session init.
Empirical verification
End-to-end: container respawned after declaring
xiaolai-marketplaceincontainer.json:plugins.plugin_install:installedfires inside the container; SDK clones marketplace into~/.claude/plugins/marketplaces/xiaolai/;installed_plugins.jsonandknown_marketplaces.jsonboth written by SDK as expected; agent'sinitevent lists the newcodex-toolkitplugin.For the failure path: declaring a private repo without the corresponding OneCLI vault github auth produces a
plugin_install:failedevent with a clear "marketplace.json not found" or "authentication failed" message — surfaced via the newplugin_install_failedprovider event.Test plan
pnpm test(host) passes — 435 tests, including 12 new ones for this changebun run typecheck(container) cleanplugin_install_failedevent → operator sees error in host log🤖 Generated with Claude Code