feat: configurable env passthrough, extra mounts, and host path trans…#5
Open
Jenriquez5 wants to merge 1 commit into
Open
Conversation
…lation Adds four non-breaking `PodmanConfig` / plugin config fields so users can run the plugin in environments that the built-in behavior does not cover: - `extraEnvPassthrough`: additional env var names to forward to the sandbox via `-e`, appended to the built-in list (`ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`). Typical use: `CLAUDE_CODE_OAUTH_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN`. - `envFallback`: a fallback value map for passthrough keys when `process.env` has none. Lets hosts whose own config store is not reflected in `process.env` still feed secrets to the sandbox (`process.env` wins when both are present). - `extraMounts`: extra `-v` mounts appended to every container, for things like SSH keys, GPG keyring, or credentials bind mounts. - `hostPathTranslations`: prefix rewrites applied to every mount source path before being handed to the container runtime. Required when the plugin runs inside a container with a mounted docker/podman socket (docker-out-of-docker): the in-container paths the plugin sees are not visible to the host runtime daemon, so they must be rewritten to the real host paths. Longest matching prefix wins. The rewrite is extracted as an exported pure function `translateHostPath` for unit testing, and is applied uniformly to `hostClaudeDir`, `workspaceDir`, and `extraMounts[i].hostPath` so callers cannot forget to translate one of the three. Tests: 9 new tests in `podman-runner.test.ts` covering `translateHostPath` (undefined/no-match/exact/suffix/longest/non-boundary), `hostPathTranslations` applied at `startDetached` time, `extraMounts` with default and explicit modes, `extraEnvPassthrough` reading from `envFallback` when `process.env` is empty, and `process.env` winning over `envFallback` when both are present. README updated with a new config options table row for each field and two worked examples (docker-out-of-docker, extra mounts + env passthrough). Full suite: 208/208 passing, tsc clean, eslint clean, build clean.
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
Adds four non-breaking
PodmanConfig/ plugin config fields so this plugin can be used in environments that the built-in behavior does not cover today:extraEnvPassthrough— additional env var names to forward to the sandbox via-e, appended to the built-in list (ANTHROPIC_BASE_URL,ANTHROPIC_AUTH_TOKEN,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC). Typical uses:CLAUDE_CODE_OAUTH_TOKEN,GH_TOKEN,GITHUB_TOKEN.envFallback— fallback value map for passthrough keys whenprocess.envhas none. Lets hosts whose own config store is not mirrored intoprocess.envstill feed secrets to the sandbox.process.envwins when both are present.extraMounts— extra-vmounts appended to every sandbox container, for SSH keys, GPG keyring, credentials bind mounts, etc. Each entry is{ hostPath, containerPath, mode? }withmodedefaulting torw.hostPathTranslations— prefix rewrites applied to every mount source path before being handed to the container runtime. Required for docker-out-of-docker setups: when the plugin runs inside a container with a mounted docker/podman socket, the paths it sees (/home/user/.claude,~/.openclaw/workspaces/...) are not visible to the host runtime daemon and must be rewritten to the real host paths. Longest matching prefix wins.Motivation
I'm running this plugin inside an OpenClaw bot container with
/var/run/docker.sockbind-mounted. Two things stop working out of the box and both need small config hooks rather than environment-specific monkey-patching ofdist/:hostClaudeDirandworkspaceDirare container-local paths that the host daemon can't resolve.hostPathTranslationsrewrites them.env.vars) is not injected into its ownprocess.env; it's only injected into skill subprocesses, which the plugin is not. WithoutenvFallback, secrets configured for the bot can't reach the sandbox, even though the user has already put them in a config file on the same machine.extraEnvPassthrough+envFallbacksolves this without tying the plugin to any particular host application.All four fields are optional, default to empty, and match the existing behavior when unset. No migration is required for existing installs.
Implementation notes
translateHostPathis exported as a pure function so the rewrite logic can be unit-tested in isolation (longest-match, exact match, suffix, non-boundary prefix). It is applied uniformly tohostClaudeDir,workspaceDir, and everyextraMounts[i].hostPath— callers cannot forget to translate one of the three.BUILTIN_PASSTHROUGH_KEYSstays intact; user keys are merged vianew Set([...builtin, ...extraEnvPassthrough])so duplicates are harmless.extraMountsentries default to:rwto match the behavior of the built-in claude home / workspace mounts.:rois available when the caller wants it.Test plan
npx tsc --noEmitcleannpx eslint src/cleannpm run buildproducesdist/claude-code.js(162.5kb)npx vitest run— 208/208 tests passing, including 9 new tests insrc/podman-runner.test.ts:translateHostPath: undefined translations, no-match, exact prefix, prefix + suffix, longest-match wins, non-boundary prefix not rewrittenstartDetachedwithhostPathTranslationsapplied to bothhostClaudeDirandworkspaceDirstartDetachedwithextraMountsusing default (rw) and explicit (ro) modesstartDetachedwithextraEnvPassthroughreading fromenvFallbackwhenprocess.envis emptyprocess.envwinning overenvFallbackwhen both presentCLAUDE_CODE_OAUTH_TOKENandGH_TOKENfromenvFallback, and anextraMountsentry for SSH keys letsssh -T git@github.comsucceed inside aclaude_code_startsession.Docs
README.mdgains one table row per new option and two worked examples:hostPathTranslationsextraMounts+ OAuth/GitHub tokens viaextraEnvPassthrough+envFallbackHappy to split this into smaller PRs (one per field) if you'd prefer that review shape — they all stack on top of the same
PodmanConfiginterface extension so I left them together.