WebCodex does not keep a persistent shell session. It prepares an environment snapshot once per project/profile and then runs each command as an independent process with that snapshot. This page describes how shell profiles work, how to configure them, and the safety boundaries.
Applies to
webcodex-agent(the host agent that executes shell commands). The server never reads or stores shell env values, init_script bodies, or tokens.
When a command runs for a project, the agent:
- Resolves the effective profile (
project.shell_profile, elseshell.default_profile, else plain shell config). - Prepares a one-time environment snapshot for that
project/cwd + profilepair: it starts the profile program withenv_clear, applies the profileenv, runs the profileinit_script(if any), and captures the resulting environment viaenv -0behind a unique marker. - Caches the snapshot keyed by
project/cwd + profile name. - Runs every subsequent command for that pair as a fresh process with the
cached snapshot applied — there is no long-lived shell, no
sourceper command, and no.bashrc/.profileloaded by default.
Because the snapshot is keyed by project path + profile name, the same profile
can be reused by multiple projects, and a project-relative init_script (e.g.
source .venv/bin/activate) is resolved from each project's own root.
WebCodex intentionally does not source ~/.bashrc or ~/.profile when
preparing a snapshot:
.bashrccan be slow (network calls, prompts, completions).- It may contain interactive-only commands (
stty,echo, prompts) that break non-interactive capture or hang the prepare step. - It can leak or pollute the environment (secrets, aliases, functions).
- It is non-reproducible across hosts and users.
Use an explicit shell profile instead. Explicit profiles are auditable, fast, and do not depend on the interactive shell setup of the agent's user.
[shell]
default_profile = "rust"
[shell.profiles.rust]
program = "sh"
args = ["-c"]
[shell.profiles.rust.env]
PATH = "/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
CARGO_HOME = "/root/.cargo"
RUSTUP_HOME = "/root/.rustup"This profile needs no init_script: the env block already sets PATH,
CARGO_HOME, and RUSTUP_HOME, so cargo / rustc are directly available.
[shell.profiles.py-venv]
program = "bash"
args = ["-lc"]
init_script = '''
source .venv/bin/activate
'''The init_script is project-relative: .venv/bin/activate is resolved
from the project root, so each project activates its own venv. The snapshot is
captured once per project/profile pair.
[shell.profiles.conda-ml]
program = "bash"
args = ["-lc"]
init_script = '''
source /opt/miniconda3/etc/profile.d/conda.sh
conda activate ml
'''A project TOML (projects.d/<id>.toml) can pin a profile:
id = "paper-exp"
path = "/root/git/paper-exp"
shell_profile = "conda-ml"The effective profile for a command is chosen as:
project.shell_profile(if set), elseshell.default_profile(if set), else- fallback to the plain shell config (no prepared snapshot).
Notes:
- The snapshot cache key is
project/cwd + profile name. - The same profile can be reused by multiple projects.
.venv(and any project-relativeinit_script) is prepared in the project root.listProjectsexposesshell_profile(the project's setting),resolved_shell_profile(the actually-used name), andshell_profile_status(configured/missing/not_configured/unknown).
There is no reload API in this phase. Changing a shell profile config requires restarting the agent so the in-memory snapshot cache is rebuilt:
Changing shell profile config requires restarting the agent so the in-memory snapshot cache is rebuilt.
After editing agent.toml or a project TOML, restart the webcodex-agent
service. Existing snapshots are dropped on restart and re-prepared lazily on
the next command.
- Never put tokens in
init_script. - Never
echo/printfsecrets ininit_script— anything the script writes to stdout is parsed as part of the env snapshot capture. - Status /
runtime_status/listAgents/listProjectsexpose only sanitized metadata: profile name,has_init_script(boolean),env_keys_count(count),program, andargs_count. They never exposeinit_scriptbodies, env values, tokens, the Authorization header, the fullagent.toml, or the full env snapshot. - A prepare failure may surface a safe error summary (e.g. "failed to
prepare shell profile 'x' at "), but never the
init_scriptbody or stderr tail. - The WebCodex agent token (
WEBCODEX_TOKEN/WEBCODEX_AGENT_TOKEN/WEBCODEX_USER_TOKEN/AUTHORIZATION) is stripped from the child process environment and is never passed to commands. prepareruns withenv_clear+ an explicit allowlist of inherited keys; profiles must declare the env they need.
Run the local agent-config doctor to validate shell profiles and project binding without contacting the server:
webcodex-cli doctor --agent-config /etc/webcodex/clients/workstation/agent.tomlAdd --strict to exit non-zero on any failure, and --project <id> to also
run a remote shell roundtrip (printf webcodex-doctor-ok) against a specific
project:
webcodex-cli doctor --agent-config /etc/webcodex/clients/workstation/agent.toml --strict
webcodex-cli doctor --server-url https://example.test \
--user-token-file ~/.config/webcodex/user.token \
--agent-config /etc/webcodex/clients/workstation/agent.toml \
--project agent:workstation:my-repo --strictThe doctor checks, locally:
agent.tomlparses.shell.default_profilereferences an existingshell.profilesentry.projects_direxists and each project TOML parses.- each project
pathexists. - each project
shell_profile(or the resolved default) is present inshell.profiles.
Remotely (when --server-url, a token, and --project are given), it runs a
minimal printf webcodex-doctor-ok roundtrip through run_shell and verifies
the marker.
See also:
- AGENT_PROJECTS.md — agent project registry.
- TROUBLESHOOTING.md — operational checklist.
- BUILD_INSTALL.md — install and status reference.