$TMPDIR/chatcli-agent-<random>/ and exposed to the agent via the CHATCLI_AGENT_TMPDIR environment variable. This solves two recurring pain points:
- The agent needs to create a temporary script (e.g. a
.shfor a complex patch) but the/coderboundary blocks writes outside the project tree, forcing the agent to pollute the repository. - When a tool result is truncated and ChatCLI saves the full content to disk with the marker
[full output saved to /tmp/...], the agent cannot read that file — the path was outside the workspace boundary.
Layout
<random> suffix comes from os.MkdirTemp — unique per process. This avoids collisions when multiple chatcli instances run in parallel on the same host.
How does the agent know it exists?
The/agent and /coder system prompt automatically receives a block with:
- The literal scratch path resolved at startup (the agent doesn’t have to expand any variable).
- Instructions to use
$CHATCLI_AGENT_TMPDIRin shell commands when that’s more convenient. - Usage pattern for truncation markers: when reading
[full output saved to /tmp/chatcli-agent-XXX/tool-results/...], open the file withread_fileinstead of re-running the original tool call.
Scenario 1 — Create and run a temporary script
In/coder, the model has two ways to stage a script:
Direct path (absolute)
validatePath recognises the aux path registered by InitSessionWorkspace and allows the write.
Shell expansion in exec
$CHATCLI_AGENT_TMPDIR expansion happens in the child shell, which inherits the variable from the ChatCLI process.
Scenario 2 — Recover the middle of a truncated output
The tool result budget truncates large tool outputs (> 20K chars by default) and saves the full content totool-results/. The preview returned to the model contains a marker like:
tool-results/ are on the agent’s read allowlist, so the model just opens the file:
Lifecycle
| Event | Action |
|---|---|
NewChatCLI (startup) | agent.InitSessionWorkspace(logger) creates the dirs, exports CHATCLI_AGENT_TMPDIR, registers the paths with the validators |
| Each agent turn | Aux paths consulted by validatePath (engine) and IsReadAllowed (read validator) |
| Tool result exceeds budget | tool-results/ receives the full content; preview with marker returns to the model |
ChatCLI.cleanup() (exit, Ctrl+D, SIGTERM) | ws.Cleanup() runs os.RemoveAll on the root dir; the env var is unset |
Configuration
| Variable | Description | Default |
|---|---|---|
CHATCLI_AGENT_TMPDIR | Read-only. Absolute path of the session scratch dir, automatically exported to subprocesses. | (set at startup) |
CHATCLI_AGENT_KEEP_TMPDIR | If true, skip cleanup and keep the files after exit (debugging). | false |
Security
The session workspace does not loosen the agent boundary against the rest of the system:- The scratch dir lives under
os.TempDir()($TMPDIRon macOS,/tmpon Linux), with0700permissions on the session root. - Only that specific directory (created via
os.MkdirTemp) is added to the allowlist — not the whole/tmp. - Validators (
engine.validatePath,SensitiveReadPaths.IsReadAllowed) keep enforcing the rest of the protections: blocks on sensitive paths (/etc/shadow,~/.ssh,~/.aws/,~/.gnupg, etc.) and the project boundary. - The
@webfetchsave_to_fileconfines writes to the scratch dir viafilepath.Base+ post-resolve check, even if the model passes an absolute path.
Next Steps
Tool Result Management
How the budget decides what to truncate and where to persist.
Subagent Delegation
Delegate heavy tasks to a subagent with isolated context.
Web Tools
@webfetch save_to_file uses the scratch dir.Environment Variables
Full list of variables that control the workspace.