Skip to main content

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.