Per-Console Tenant Isolation (Shell Integration)
This document describes an optional CLI feature that lets each of your terminal windows maintain its own independent tenant context. It is most useful if you regularly work across multiple Codiac tenants and want to keep them open side-by-side without one console's login state affecting another.
Who needs this
You do not need this feature if you work with a single Codiac tenant, or if you are
comfortable switching tenant context in a single terminal window using cod tenant switch.
You will benefit from this feature if you:
- Manage more than one Codiac tenant (e.g., a client tenant and an internal tenant, or separate staging and production tenants hosted as distinct Codiac tenants)
- Routinely keep multiple terminal windows or panes open at the same time
- Want changes in one console — logging in, switching tenant — to have no effect on any other console that is currently open
What it does
When shell integration is active, each terminal window generates a unique session ID the
moment it opens. Every cod command run in that window reads and writes its tenant context
to a session file keyed to that ID (~/.codiac/sessions/<session-id>.json). Because each
window has its own ID, their context files never collide.
Without shell integration, all windows share the same global context file
(~/.codiac/usercontext.json), so a cod login or cod tenant switch in one window
immediately affects every other open window.
Setup
Add a single line to your shell's startup file. Pick the block for your shell:
bash
# Add to ~/.bashrc (or ~/.bash_profile on macOS)
eval "$(cod shell init)"
zsh
# Add to ~/.zshrc
eval "$(cod shell init)"
fish
# Add to ~/.config/fish/config.fish
cod shell init | source
PowerShell
# Add to your PowerShell profile ($PROFILE)
Invoke-Expression (cod shell init)
After saving the file, open a new terminal window (or source the file in an existing
one). From that point on, every new window gets its own isolated context.
How to verify it is working
Run the following in two separate terminal windows:
Window A:
cod tenant switch <tenant-A>
cod whoami
Window B:
cod tenant switch <tenant-B>
cod whoami
Each cod whoami should report a different tenant. Without shell integration both would
show the same tenant — whichever was switched to last.
Technical note
Why this approach was chosen
The CLI needs a way to identify which terminal window a command was run from, so it can
maintain separate state per window. The naive choice is to use the process ID of the
parent shell — every cod invocation is a child of the shell that ran it, so the shell's
PID should be a stable per-window identifier.
This works correctly on Unix and macOS, where the shell (bash, zsh, etc.) launches the
cod Node.js process directly, making process.ppid the shell's PID. On Windows,
however, oclif wraps every CLI command in a .cmd batch file shim. The actual launch chain
is:
PowerShell / cmd.exe (the terminal — stable, what we want)
└── cod.cmd (the batch shim — a new process each invocation, dies immediately)
└── node.exe (the actual CLI — process.ppid points here, not to the terminal)
Because the batch shim is a short-lived intermediate process, process.ppid on Windows
always points to the shim's PID, which changes on every command. The terminal's PID is
one level further up and not directly accessible from Node without a process-tree walk.
Walking the process tree to find the real shell is technically possible but requires
platform-specific system calls (WMI on Windows, /proc on Linux) and adds cross-platform
complexity for little gain.
The standard solution used by tools such as nvm, rbenv, pyenv, direnv, and
starship is shell integration: the shell itself generates a stable ID once at startup
and exports it as an environment variable. Every child process — including all cod
invocations in that window — inherits the variable automatically. The CLI then uses it as
the session key.
cod shell init generates a UUID and emits the appropriate export statement for the
detected shell. Because the UUID is generated fresh when the terminal opens (via the
startup file) and is exported into the shell environment, it is stable for the lifetime of
that terminal session and unique across all concurrently open windows.
For users who have not yet configured shell integration, the CLI falls back to
ppid-<N>.json keying — imperfect on Windows but functional on Unix — so the feature
degrades gracefully rather than breaking.