claude-vm-config-global
Interactively create the global claude-vm config PAIR at ~/.config/claude-vm/config-bake.yml + config-boot.yml from the resolved defaults (cpus 2, mem 4096, bundled-tinyproxy proxy default, egress.allow incl. api.anthropic.com, claude.version). Idempotent — detects existing files and offers to merge or leave rather than clobber.
- License 许可
- GPL-3.0
- 最近更新
- 2026-11-14
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- claude-vm-config-global
- description
- Interactively create the global claude-vm config PAIR at ~/.config/claude-vm/config-bake.yml + config-boot.yml from the resolved defaults (cpus 2, mem 4096, bundled-tinyproxy proxy default, egress.allow incl. api.anthropic.com, claude.version). Idempotent — detects existing files and offers to merge or leave rather than clobber.
03 SKILL.md 正文
claude-vm-config-global
You are running the /claude-vm-config-global skill. Your job is to
create the global claude-vm config pair at
~/.config/claude-vm/config-bake.yml and
~/.config/claude-vm/config-boot.yml (expand ~ to the user's home
directory) from the resolved defaults.
Since issue #179, claude-vm's config is split into a bake file and a boot file per tier (four files total, all optional):
config-bake.yml— keys that change bytes in the guest.rawimage:packages:(a flat list of apt packages baked into the image),apt_sources:(third-party apt repos rendered into the image), andimage.root_headroom_mb. The image cache key + filename is a whole-file, raw-byte hash of the bake files, so editing a bake file (including only its trailing newline) rebuilds the image.config-boot.yml— keys applied at run time (launcher/VM wiring):cpus,mem,proxy.*,egress.allow,mounts,repo.*,packages:here means installed at boot (a flat list),update_at_boot,add_apt_uris_to_allowlist,claude.*, andgithub.*. Boot files never affect image identity.
This pair is the machine-wide layer of claude-vm's four-file config (the
per-repo pair at <repo>/.claude-vm/config-{bake,boot}.yml, written by
/claude-vm-config-repo, overrides it). The config surface, layering
semantics, and key meanings are documented in the sibling claude-vm
skill (skills/claude-vm/SKILL.md) and the annotated
payload/config-bake.example.yml / payload/config-boot.example.yml.
This skill writes the global layer with the resolved defaults rather
than the examples' illustrative placeholders.
Migration: if a legacy single-file ~/.config/claude-vm/config.yml
exists, the launcher aborts with a migration message (it will not
silently read it). When this skill detects that legacy file, offer to
migrate: split its keys into the bake/boot pair per the placement rule
above, write the pair, and tell the user to remove or rename the legacy
config.yml afterward.
Idempotent — detect and offer, never clobber
This skill is idempotent. Before writing anything it checks whether
~/.config/claude-vm/config-bake.yml and
~/.config/claude-vm/config-boot.yml already exist (each independently):
-
If a file does not exist: this is the create path for that file. Propose the full default file, get approval, write it.
-
If a file already exists: do not clobber it. Read it, show the user what is there, and offer these choices:
- Leave the existing file untouched (the default, safe choice).
- Merge the resolved defaults in for any keys the existing file is missing, preserving every key the user already set.
Never overwrite a key the user already set on the "merge" path, and never delete a key. A merge only fills gaps. If the existing file already has every key the defaults provide, report that it is already complete and leave it untouched.
Writing either file requires explicit user approval in every case.
Resolved defaults
These are the values this skill writes, drawn from the parent issue's
verified analysis. They deliberately override the illustrative
values in payload/config-bake.example.yml /
payload/config-boot.example.yml. The file column names which of
the two global files each key is written into (its bake/boot placement):
| key | file | default | rationale |
|---|---|---|---|
cpus |
boot | 2 |
RAM-bound sizing; vCPUs time-slice, 2 covers git/build/test bursts |
mem |
boot | 4096 |
the real ceiling — RAM is committed, ~8–12 VMs fit at 4 GB on a 64 GB host |
proxy.cmd |
boot | omitted (bundled tinyproxy launcher is the launcher-side default) | tinyproxy is the chosen forward proxy; the launcher runs the bundled payload/proxy/tinyproxy-launch.sh when proxy.cmd is unset |
proxy.port |
boot | 3128 |
matches the launcher default |
proxy.host_alias |
boot | 192.168.127.254 |
the gvproxy host alias the guest reaches the proxy on |
egress.allow |
boot | api.anthropic.com, github.com, claude.ai, downloads.claude.ai |
api.anthropic.com is required for Remote Control; the rest cover git + claude install/fetch |
claude.version |
boot | stable |
which claude binary the host-side verified cache fetches |
claude.renderer |
boot | omitted (claude's own default) | terminal renderer on the interactive console: classic | fullscreen | unset |
claude.remote_control |
boot | omitted (false) |
opt-in Remote Control: true adds --remote-control + a date-stamped --name default; false/unset passes CLI args through |
packages: (boot file) |
boot | [] |
apt packages installed AT BOOT (a flat list; through the proxy, before claude starts) |
update_at_boot |
boot | true |
apt-get update && upgrade at boot |
add_apt_uris_to_allowlist |
boot | auto |
add derived egress URIs to the proxy allowlist only when boot-time package work needs them (auto), or always (always) |
apt_sources (boot file) |
boot | [] |
third-party apt repos a boot-time install pulls from (union+dedup with the bake file's) |
claude.permission_mode |
boot | bypassPermissions |
in-guest Claude's permission mode (bypassPermissions | default only; any other value aborts the launch) |
claude.plugins.install_at_boot |
boot | [] |
plugin@marketplace refs the guest installs at boot, blocking, before claude starts |
claude.plugins.update_at_boot |
boot | true |
refresh the marketplaces and update the installed plugins at boot — the freshness path for BAKED plugins |
claude.plugins.add_marketplace_uris_to_allowlist |
boot | auto |
marketplace-URI analogue of add_apt_uris_to_allowlist |
claude.plugins.enabled |
boot | omitted | optional map (plugin ref → boolean) mirroring settings.json's enabledPlugins; overrides the default-enabled state per plugin (false = installed-but-disabled) |
claude.marketplaces (boot file) |
boot | [] |
marketplaces only an install_at_boot ref needs (union+dedup with the bake file's, by name); the url only has to be reachable from the GUEST — the build pre-registers it best-effort and a failure there only warns |
github.auth |
boot | none |
whether the guest is seeded with a GitHub auth token derived from the host |
packages: (bake file) |
bake | [] |
apt packages BAKED into the guest image (a flat list; present with no network at boot) |
apt_sources (bake file) |
bake | [] |
third-party apt repos rendered into the image at build time |
claude.marketplaces (bake file) |
bake | [] |
marketplaces registered INTO the image at build time (union+dedup with the boot file's, by name); registering one declared HERE is a build precondition — a failed add aborts the build, where a boot-declared one only warns |
claude.plugins.bake |
bake | [] |
plugin@marketplace refs INSTALLED into the image at build time; a BAKE key because they change the image's bytes |
image.root_headroom_mb |
bake | 1024 |
extra MiB of FREE SPACE in the guest root filesystem above its base content, so a live session (boot-time apt working set + ordinary growth) does not hit ENOSPC |
env.set (bake file) |
bake | omitted | guest environment variables written INTO the image, as explicit literals. NON-SECRET only — a bake value is committed config AND image bytes, and persists across every run |
env.set (boot file) |
boot | omitted | same shape, applied per launch and shredded on exit; overrides the bake value for the same name |
env.copy |
boot | omitted | NAMES read from the environment claude-vm is launched from and forwarded to the guest. BOOT-ONLY — a hard abort in a bake file. This is how a third-party API key reaches the guest without ever being written to config |
env.files |
boot | omitted | host .env files parsed at launch and forwarded the same way. BOOT-ONLY — a hard abort in a bake file |
Notes on the forward-looking keys:
-
proxy.cmdis omitted from the written config. tinyproxy is the chosen forward proxy, and the launcher already defaults to the bundledpayload/proxy/tinyproxy-launch.shwhenproxy.cmdis unset (it reads the egress allowlist from$CLAUDE_VM_EGRESS_ALLOWLISTand binds$CLAUDE_VM_PROXY_PORT). So the resolved global config leavesproxy.cmdunset rather than pinning a brittle invocation. Write aproxy.cmdonly if the user wants to override the bundled launcher; any override must read$CLAUDE_VM_EGRESS_ALLOWLISTrather than a hand-maintained list baked into the command. -
claude.version: stableselects whichclaudebinary the host-side GPG-verified cache fetches. It is consumed bypayload/lib/claude-cache.sh: the host resolves the channel/pin to a concrete version, downloads that version's GPG-signed manifest, verifies the signature against the operator's pinned key, checksum-verifies the binary, caches it keyed on the version, and shares it into the guest, where the image's fstab mounts itro.stable(default) tracks the conservative stable channel;latesttracks the latest channel; a dotted version like2.1.172pins one concrete release with no channel resolution. -
api.anthropic.commust stay inegress.allow. Remote Control is outbound-HTTPS-only and connects to the Anthropic API on 443; dropping this host breaks every in-guest Remote Control session. Treat it as load-bearing, not optional. -
claude.rendereris omitted by default. It selects the in-guest claude's terminal renderer on the interactive console (classic→CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1;fullscreen→CLAUDE_CODE_NO_FLICKER=1); unset passes nothing so claude uses its own default. Both renderers work over the byte-pipe console, so leaving it unset is a fine default — write a value only if the user asks for one. An unrecognized value aborts the launch. -
claude.remote_controlis omitted by default (false). It is an opt-in boolean: whentrue, the launcher injects--remote-controlinto the in-guest claude invocation (unless the CLI args already carry it) and, when Remote Control is in effect but no--namewas given, appends a date-stamped--name(format likeJul10-14:30) so the run is named. Whenfalse/unset, claude runs without Remote Control and the user's CLI args pass through unchanged. Acceptstrue/false(or leave unset); any other value aborts the launch. Ask the user whether they want Remote Control on by default; write the key only when they opt in. -
The bake file's
packages:/apt_sources:and the boot file'spackages:are written empty ([]) by default. Ask the user whether they want any apt packages baked into the image, installed at boot, or any third-party apt repos; write entries only if they name specific packages/repos. Leaving these empty is the safe default — the bake file's entries are baked into the image (issue #105) and the boot file's are installed at boot through the proxy (issue #106). -
claude.permissions.allow/.ask/.denyandclaude.marketplaces/claude.plugins.bake/.plugins.install_at_bootare written empty by default. Same reasoning: ask whether the user wants specific permission rules, marketplaces, or plugins baked/installed; write entries only on request.claude.permission_modeandclaude.permissions.*are rendered into the guestsettings.json(issue #104).claude.plugins.bakerefs are installed into the image by the build's plugin bake step and.install_at_bootrefs by the guest boot launcher's plugin phase (issue #107); both are also rendered intosettings.json'senabledPlugins. -
Placement is enforced for
claude.plugins.bakebelongs inconfig-bake.yml(baked plugins change the image's bytes, so they must live where the whole-file image-identity hash sees them);install_at_boot,update_at_boot,add_marketplace_uris_to_allowlist, andenabledbelong inconfig-boot.yml. A sub-key written into the wrong file aborts the launch with a message naming the file that carries it, rather than parsing and being silently ignored. It is a presence test, so a misplaced sub-key written empty (a valuelessbake:, orbake: []/""/{}) aborts exactly like a populated one — never write a placeholder sub-key into the file it does not belong in.claude.marketplacesis the exception: allowed in both files, unioned and deduped byname, with the same name under two differing urls aborting the launch. Give every entry aname:— the name is what aplugin@marketplaceref resolves against and what registration is checked by, so an entry with a url and no name aborts the launch too (issue #226). Which file it lands in still decides its build-time failure policy — a bake-file entry must register during the image build or the build aborts, so its url has to be reachable from the build container, while a boot-file entry is pre-registered best-effort and a url the build cannot reach only warns (issue #226). -
Prefer an explicit
https://marketplace url. The launcher derives the guest's marketplace egress hosts from the url; theowner/repoGitHub shorthand yields no derivable host, so it needsgithub.comkept inegress.allowby hand. -
claude.plugins.enabledis omitted by default. It is an optional map of plugin ref → boolean that mirrorssettings.json's ownenabledPluginsvocabulary and is rendered straight into the guestsettings.json(issue #104). Every ref inclaude.plugins.bake/.install_at_bootis enabled by default, so write anenabledentry only when the user wants to override that — e.g.falseto ship a plugin installed-but-disabled (toggling a debug plugin likeshow-loaded-rulesaround a specific issue). Keys must name an installed ref and values must be boolean; a typo aborts the launch. -
The whole
env:block is omitted by default. Ask the user whether the in-guest claude needs any environment variables — a plain setting (FOO_ENDPOINT) or a third-party API key for a tool, MCP server or model provider other than Anthropic/GitHub (OPENROUTER_API_KEY,TAVILY_API_KEY). Then place each by how its value is obtained, which is what decides the tier:- a literal the user dictates →
env.set, in either file. Only write it intoconfig-bake.ymlwhen it should persist in the image for every session; a bake value is committed config and image bytes, so never put a secret there. - a value that already lives in the user's shell environment →
env.copy, listing the NAME only. Never ask for the value and never write it anywhere; the launcher reads it at launch.env.copyin a bake file is a hard abort. - a value that already lives in a host
.envfile →env.files, listing the path. Also boot-only, also a hard abort in a bake file.
Precedence, lowest to highest:
bake env.set<boot env.files<boot env.copy<boot env.set.Pair every
env.copywithegress.allow. Forwarding a key does not make the service reachable — the guest is egress-confined, so the provider's host must be inegress.allow(openrouter.aiforOPENROUTER_API_KEY,api.tavily.comforTAVILY_API_KEY, …) or the proxy blocks the call and the failure is baffling. Nothing is derived automatically: a variable name implies no hostname the way an apt source's URI does. Add both or neither.These entries are written verbatim, and the launcher rejects several shapes, so check before writing: an
env.copyname that is not currently exported in the user's shell aborts the launch (confirm with the user that they export it — never print or capture the value); anenv.filespath that is not on the host aborts; a name outside[A-Za-z_][A-Za-z0-9_]*aborts; anenv.setvalue that is a map or a list aborts, and so does a key left valueless (writeNAME: ""for the empty string); and a name claude-vm's own launcher composes — every name inrun.env(the proxy vars,CLAUDE_ARGS, the*_TAGmount vars,IS_SANDBOX,DISABLE_AUTOUPDATER, the rendererCLAUDE_CODE_*vars, the terminal geometry, theCLAUDE_VM_*_UPDATE_AT_BOOTflags) plusCLAUDE_VM_LAST_CLAUDE_STATUS— aborts, because the guest sourcesrun.envbefore the env files, so such an entry would overwrite the launcher's own value and break the boot;CLAUDE_VM_LAST_CLAUDE_STATUSis the exception in the other direction (exported long after both files, so an entry for it would be silently overwritten instead) and is refused at load for that reason. - a literal the user dictates →
-
github.authdefaults tonone. Ask the user whether they want the guest seeded with a GitHub auth token derived from the host (host-token);noneis the safe default since the consumer that seeds the token lands in a sibling slice under #39.
Steps
Follow these in order. Do not write the file until the user has explicitly approved the proposed content.
Step 1: Resolve the target paths
Expand ~ to the user's home directory. There are TWO target files:
~/.config/claude-vm/config-bake.yml
~/.config/claude-vm/config-boot.yml
Respect XDG_CONFIG_HOME if set: the config dir is
${XDG_CONFIG_HOME:-$HOME/.config}/claude-vm, matching the launcher's
CLAUDE_VM_GLOBAL_CONFIG_DIR default in payload/lib/config.sh. The
parent directory may not exist yet; the Write tool creates it.
Also check for a legacy ${XDG_CONFIG_HOME:-$HOME/.config}/claude-vm/config.yml.
If it exists, tell the user the launcher no longer reads it and offer to
migrate its keys into the bake/boot pair (splitting by the placement
rule); after writing the pair, remind them to remove or rename the
legacy file.
Step 2: Detect existing config files
Check each of the two target files independently.
-
Absent → create path for that file. The proposed content is the full default file from Step 3.
-
Present → idempotent path for that file. Read its full contents, parse the YAML, and show the user what is already there. Then ask (via
AskUserQuestion) whether to:- Leave it untouched (recommended default), or
- Merge the resolved defaults into any keys it is missing.
On "leave", skip that file. On "merge", compute the gap-fill (Step 3, merge variant) and continue to Step 4 for that file.
Step 3: Compose the proposed content
Create variant (no existing file). The full default config-bake.yml
(image-bytes keys) is:
# claude-vm global BAKE config (machine-wide defaults).
#
# Keys here change BYTES in the guest .raw image, so the image cache key +
# filename is a WHOLE-FILE, RAW-BYTE hash of this file. Editing it (including
# only its trailing newline) rebuilds the image. The per-repo bake layer at
# <repo>/.claude-vm/config-bake.yml overrides scalars and unions lists. See the
# claude-vm skill (skills/claude-vm/SKILL.md) and payload/config-bake.example.yml.
#
# NO SECRETS HERE.
# apt packages baked into the guest image (flat list of names; present with no
# network at boot). Write entries only if the user names specific packages.
packages: []
# Third-party apt repos rendered into the image at build time:
# {name, repo, key_url}. Also allowed in the boot file (union, dedup by name).
# Write entries only on request.
apt_sources: []
# Extra MiB of FREE SPACE in the guest root filesystem above its base content,
# so a live session does not hit ENOSPC (default 1024). A BAKE key: it sizes
# the .raw, so changing it rebuilds the image.
image:
root_headroom_mb: 1024
# Plugin marketplaces registered INTO the image, and the plugin@marketplace refs
# installed into it, at build time. BAKE keys: baked plugins change the image's
# bytes. `name` must match the marketplace's OWN manifest name; prefer an
# explicit https:// url so the launcher can derive its egress host. Write
# entries only on request.
claude:
marketplaces: []
plugins:
bake: []
# Guest environment variables written INTO the image (explicit literals only).
# Omitted unless the user names specific variables. NO SECRETS: a value here is
# committed config AND image bytes, and persists across every run of the image.
# env.copy / env.files are BOOT-ONLY and abort the launch if written here.
# env:
# set:
# FOO_ENDPOINT: https://foo.internal/v2
The full default config-boot.yml (run-time keys) is:
# claude-vm global BOOT config (machine-wide defaults).
#
# Keys here are applied at run time (launcher/VM wiring) and NEVER affect the
# image identity -- editing this file triggers no rebuild. The per-repo boot
# layer at <repo>/.claude-vm/config-boot.yml overrides scalars and unions lists.
# See the claude-vm skill (skills/claude-vm/SKILL.md) and
# payload/config-boot.example.yml.
#
# NO SECRETS HERE. The host claude.ai OAuth credential is mounted into the guest
# at runtime by the launcher; it is never read from or written to this file.
cpus: 2
mem: 4096
proxy:
# proxy.cmd is intentionally OMITTED: tinyproxy is the chosen forward proxy,
# and the launcher defaults to the bundled tinyproxy launcher
# (payload/proxy/tinyproxy-launch.sh) when proxy.cmd is unset. It reads the
# egress allowlist from $CLAUDE_VM_EGRESS_ALLOWLIST and binds
# $CLAUDE_VM_PROXY_PORT. Set proxy.cmd only to override that default.
port: 3128
host_alias: 192.168.127.254
egress:
allow: # outbound hosts permitted through the proxy
- api.anthropic.com # REQUIRED for Remote Control (outbound 443)
- github.com
- claude.ai
- downloads.claude.ai
# apt packages installed AT BOOT (flat list; through the proxy, before claude
# starts). Write entries only on request.
packages: []
update_at_boot: true # apt-get update && upgrade at boot (default true)
add_apt_uris_to_allowlist: auto # auto (default) | always
# apt_sources: third-party apt repos a boot-time install pulls from (union+dedup
# with the bake file's). Write only on request.
apt_sources: []
claude:
version: stable # which claude binary the host-side GPG-verified cache
# fetches: stable (default) | latest | <pinned>
# renderer: classic # classic (no alt-screen) | fullscreen | unset. Omitted.
# remote_control: false # opt-in Remote Control. Omitted by default.
# permission_mode + permissions.* render into the guest settings.json's
# permissions (issue #104); the host's own ~/.claude/settings.json is never read.
permission_mode: bypassPermissions # bypassPermissions (default) | default
permissions:
allow: [] # write entries only if the user names specific rules
ask: []
deny: []
marketplaces: [] # {name, url} entries a boot-time install needs (union+
# dedup by name with the bake file's). One declared HERE
# only has to be reachable from the GUEST -- the build
# pre-registers it as an optimization and a url it cannot
# reach only warns. Write on request.
plugins:
# bake: is a BAKE key -- it lives in config-bake.yml. Writing it here aborts
# the launch, on a PRESENCE test: even an empty `bake:` placeholder aborts.
install_at_boot: [] # plugin@marketplace refs installed at boot, blocking,
# before claude starts. Write only on request.
update_at_boot: true # refresh the marketplaces and update the installed
# plugins at boot; the freshness path for BAKED plugins
add_marketplace_uris_to_allowlist: auto # auto (default) | always
# enabled: OPTIONAL map, plugin ref -> boolean, mirroring settings.json's
# enabledPlugins. Every bake/install_at_boot ref defaults enabled; write an
# entry only to override (false = installed-but-disabled). Write on request.
# enabled:
# show-loaded-rules@thevoskamps: false
# Guest GitHub auth.
github:
auth: none # none (default) | host-token
# Guest environment variables. Omitted unless the user names specific ones.
# `set` holds NON-SECRET literals (they are committed config); `copy` names
# host environment variables read at launch, and `files` names host .env files
# parsed at launch -- neither ever writes a secret into this file. Pair every
# `copy` entry with the provider's host in egress.allow above, or the
# egress-confined guest cannot reach the service the key is for.
# env:
# set:
# FOO_ENDPOINT: https://foo.internal/v2
# copy:
# - OPENROUTER_API_KEY
# files:
# - ~/.config/foo/prod.env
On
proxy.cmd: the bundled tinyproxy launcher (payload/proxy/tinyproxy-launch.sh) is the launcher-side default, so the resolved global boot config leavesproxy.cmdunset. If the user wants their own forward proxy, capture theirproxy.cmdverbatim into the boot file — the only hard requirement is that it honor the launcher-provided allowlist ($CLAUDE_VM_EGRESS_ALLOWLIST) rather than a baked-in one.
Merge variant (existing file): start from the existing parsed YAML
and add only the keys from that file's default that are absent. Keep
every existing key and value verbatim, including any the user
customized and any this skill does not recognize. For list keys
(bake file: packages, apt_sources, claude.marketplaces,
claude.plugins.bake; boot file: egress.allow,
packages, apt_sources, claude.permissions.allow/.ask/.deny,
claude.marketplaces, claude.plugins.install_at_boot, env.copy,
env.files), union
the default entries in (do not drop the user's extras, do not
duplicate). Render the merged result preserving the user's existing
comments where practical.
Step 4: Show the proposed file(s) and get approval
Render each full proposed file exactly as it will land on disk. For a merge variant, also show a short summary: which keys this run adds, which it preserves untouched.
Then ask explicitly, per file:
Write
~/.config/claude-vm/config-bake.yml(and/orconfig-boot.yml) as shown? (y to proceed, or tell me what to change)
Wait for explicit approval. If the user asks for changes, adjust and re-render here.
Step 5: Write the file(s)
On approval, use the Write tool to write each full file in one call.
The parent directory is created if needed.
Step 6: Verify and summarize
After each Write, re-read the file and confirm it parses as YAML and
contains the expected keys:
- config-bake.yml:
packages(list),apt_sources(list),image.root_headroom_mb,claude.marketplaces(list),claude.plugins.bake(list). - config-boot.yml:
cpus: 2,mem: 4096,proxy.port,egress.allowincludingapi.anthropic.com,claude.version,claude.permission_mode,update_at_boot,github.auth.proxy.cmdis intentionally absent — the launcher defaults to the bundled tinyproxy launcher when unset.
This is content verification — Write already errors if the bytes did
not land; the re-read confirms the intended content.
Report back:
- The absolute path(s) written (or "left untouched" / "already complete").
- For a merge, which keys were added vs. preserved.
- A reminder that the per-repo pair
(
<repo>/.claude-vm/config-{bake,boot}.yml, written by/claude-vm-config-repo) overrides scalars and unions lists, that editing a bake file rebuilds the image while editing a boot file does not, and that no secret is ever written here.
Hard constraints
- Idempotent. Never clobber an existing config. Detect it, and offer leave (default) or gap-fill merge. A merge fills gaps only — it never overwrites or deletes a key the user set.
api.anthropic.comstays inegress.allow. It is required for Remote Control. Never drop it from the defaults.- No secrets in this file. The host OAuth credential is mounted at runtime, never written to config.
- Never write without explicit approval in Step 4.
- Write exactly the two global files:
~/.config/claude-vm/config-bake.ymland~/.config/claude-vm/config-boot.yml. This skill touches no repo, edits no.gitignore, and runs no git commands. - Respect the bake/boot placement rule. Image-bytes keys
(
packagesbaked in,apt_sources,image.root_headroom_mb,claude.plugins.bake,env.setwhen it should persist in the image) go inconfig-bake.yml; run-time keys go inconfig-boot.yml. A misplaced key loudly does nothing rather than silently poisoning the image cache, and a misplacedclaude.pluginssub-key aborts the launch outright — as doenv.copyandenv.filesin a bake file, on a presence test, so even an emptyenv.copy:there aborts. - Never write a secret value into
env.set. A third-party API key goes inenv.copy(name only) orenv.files(path only). Do not ask the user to paste a key, and do not read one out of their environment to echo it back —env.copyexists precisely so the value never reaches the config or this conversation.
04 同仓库更多 Skills
mask-inappropriate-language
Replace inappropriate language (profanity, slurs, strong insults) in provided text or a file with asterisks, character-for-character, preserving everything else.
theorem-agents-interface
The interface between the sdlc reviewer and the theorem agents it spawns — what each double-dash brief parameter means, and what each consequence class means. Preloaded into every theorem agent (sdlc:theorem-generator, the disprover, and the verifier) via its skills frontmatter, and read by name by the sdlc:theorem-based-pr-reviewer agent for what its briefs' parameters mean; not invoked from the user's slash menu.
sdlc-config-user
Interactively create or merge-update the repo user sdlc config, `<repo-root>/.sdlc/user-config.yml` (tier 3 — this user, this repo, never committed), and add it to the repo's tracked `.gitignore`.
sdlc-config-repo
Interactively create or merge-update the shared repo sdlc config, `<repo-root>/.sdlc/repo-config.yml` (tier 2 — tracked, every user of this repo).
sdlc-config-global
Interactively create or merge-update the global user sdlc config, `$XDG_CONFIG_HOME/sdlc/user-config.yml` (tier 1 — this user, every repo on this machine).
pr-read-cli-interface
The contract for sdlc's PR-read CLIs — sdlc-pr-round, sdlc-pr-adjustments, sdlc-fixer-brief and sdlc-records-chain — their arguments, exit codes and output grammar. Preloaded into sdlc:theorem-based-pr-reviewer, sdlc:issue-fixer, sdlc:docs-writer and sdlc:pr-finalizer via their skills frontmatter; not invoked from the user's slash menu.