Skip to content
Environment Variables

Environment Variables

Sortie supports SORTIE_* environment variable overrides for most configuration fields, with optional .env file loading. Environment variables flow in six distinct directions, each covered in its own section below.

SectionDirectionWhen it matters
Configuration overridesParent shell / .env file → config fieldsDeploying in containers, CI, cloud-native environments
Agent runtime variablesParent shell → agent subprocessBefore starting Sortie
$VAR indirection in WORKFLOW.mdParent shell → config fields at startupWriting the workflow file
Hook subprocess environmentSortie → hook subprocessWriting hook scripts and reaction triage scripts
MCP server environmentWorker → .sortie/mcp.json → agent runtime → MCP serverWriting custom tools, debugging tool execution
Install script variablesParent shell → install.shInstalling the binary

Configuration overrides

Each SORTIE_* environment variable below overrides one WORKFLOW.md configuration field. Set them in the parent shell, in a .env file, or both.

Precedence

Four sources feed configuration, highest priority first:

  1. SORTIE_* environment variables in the real process environment
  2. .env file values (opt-in via SORTIE_ENV_FILE or --env-file)
  3. WORKFLOW.md front matter YAML
  4. Built-in defaults

A real env var always beats a .env value for the same key. Both beat whatever the YAML says.

Tracker variables

Env varOverridesType
SORTIE_TRACKER_KINDtracker.kindstring
SORTIE_TRACKER_ENDPOINTtracker.endpointstring
SORTIE_TRACKER_API_KEYtracker.api_keystring (secret, never logged)
SORTIE_TRACKER_PROJECTtracker.projectstring
SORTIE_TRACKER_ACTIVE_STATEStracker.active_statescsv
SORTIE_TRACKER_TERMINAL_STATEStracker.terminal_statescsv
SORTIE_TRACKER_QUERY_FILTERtracker.query_filterstring
SORTIE_TRACKER_HANDOFF_STATEtracker.handoff_statestring
SORTIE_TRACKER_NO_CHANGE_STATEtracker.no_change_statestring
SORTIE_TRACKER_IN_PROGRESS_STATEtracker.in_progress_statestring
SORTIE_TRACKER_COMMENTS_ON_DISPATCHtracker.comments.on_dispatchbool (true/false/1/0)
SORTIE_TRACKER_COMMENTS_ON_COMPLETIONtracker.comments.on_completionbool
SORTIE_TRACKER_COMMENTS_ON_FAILUREtracker.comments.on_failurebool

Polling variables

Env varOverridesType
SORTIE_POLLING_INTERVAL_MSpolling.interval_msint

Workspace variables

Env varOverridesType
SORTIE_WORKSPACE_ROOTworkspace.rootstring (path, ~ expanded)
SORTIE_WORKSPACE_RETENTION_DAYSworkspace.retention_daysint (days)

Agent variables

Env varOverridesType
SORTIE_AGENT_KINDagent.kindstring
SORTIE_AGENT_COMMANDagent.commandstring
SORTIE_AGENT_TURN_TIMEOUT_MSagent.turn_timeout_msint
SORTIE_AGENT_READ_TIMEOUT_MSagent.read_timeout_msint
SORTIE_AGENT_STALL_TIMEOUT_MSagent.stall_timeout_msint
SORTIE_AGENT_STOP_GRACE_MSagent.stop_grace_msint
SORTIE_AGENT_MAX_CONCURRENT_AGENTSagent.max_concurrent_agentsint
SORTIE_AGENT_MAX_TURNSagent.max_turnsint
SORTIE_AGENT_MAX_RETRY_BACKOFF_MSagent.max_retry_backoff_msint
SORTIE_AGENT_MAX_SESSIONSagent.max_sessionsint
SORTIE_AGENT_MAX_TOKENSagent.max_tokensint
SORTIE_AGENT_MAX_CONSECUTIVE_ABSENCESagent.max_consecutive_absencesint

Top-level variables

Env varOverridesType
SORTIE_DB_PATHdb_pathstring (path, ~ expanded)

Control variables

These are not config field overrides. They control how overrides are loaded.

Env varPurposeType
SORTIE_ENV_FILEPath to a .env file containing SORTIE_* overridesstring

When --env-file is provided, the CLI resolves the path to absolute and exports it as SORTIE_ENV_FILE in the process environment. This ensures the value is captured by the SORTIE_* prefix scan and propagated to the MCP server, which runs in a different working directory and needs the absolute path to locate the .env file. When both SORTIE_ENV_FILE and --env-file are set, the CLI flag wins.

Type coercion

TypeRuleError behavior
stringUsed as-is-
intParsed as an integer, with leading and trailing whitespace trimmed first.Startup error: config: polling.interval_ms: invalid integer value: abc (from SORTIE_POLLING_INTERVAL_MS)
boolAccepts true, false, 1, 0 (case-insensitive)Startup error naming the env var and rejected value
csvComma-separated. Items trimmed. Empty items discarded. Empty string produces an empty list.-

A numeral outside the range an integer setting accepts is itself a parse failure, reported the same way as the int row above: config: agent.max_turns: value is outside the range an integer setting accepts, -9223372036854775808 to 9223372036854775807 (from SORTIE_AGENT_MAX_TURNS). See the errors reference for the general form.

A value that parses successfully can still be rejected by configuration validation; the table above covers parse failures only. SORTIE_AGENT_TURN_TIMEOUT_MS is one such field, with the constraint documented in the configuration reference.

Fields not overridable via env

FieldReason
hooks.* (all hook scripts)Multiline shell scripts do not fit in a single env var
hooks.timeout_msGrouped with hooks for consistency
agent.max_concurrent_agents_by_stateComplex map structure ({"in progress": 3, "to do": 1})
tracker.api_versionNo override variable exists; set directly in WORKFLOW.md or via $VAR indirection
tracker.handoff_evidenceNo override variable exists; set directly in WORKFLOW.md. Unlike most of its neighbors in the tracker section, it does not resolve $VAR references either.
ci_feedback.*No override variables exist; must be set in WORKFLOW.md
self_review.*No override variables exist; verification commands are security-sensitive and must come from version-controlled WORKFLOW.md
reactions.* (including reactions.label_commands)No override variables exist; reaction configuration must come from WORKFLOW.md
dispatch.*No override variables exist; rule definitions and template paths must come from WORKFLOW.md
notificationsNo override variables exist; backend configuration must come from WORKFLOW.md, though $VAR references inside an entry still resolve
Extension sections (server, worker, claude-code, etc.)Plugin-owned configuration; overrides belong to the adapter
logging.levelControlled by the --log-level CLI flag
logging.formatControlled by the --log-format CLI flag

.env file support

Loading a .env file is opt-in.

Warning

Sortie does not auto-discover .env files in the working directory. Its working directory is the WORKFLOW.md location, and a .env file placed there could silently alter behavior for any operator who runs sortie from that directory. Always load .env explicitly via SORTIE_ENV_FILE or --env-file.

Enable .env loading with either:

# Via environment variable
export SORTIE_ENV_FILE=/etc/sortie/prod.env
sortie WORKFLOW.md

# Via CLI flag (takes precedence over the env var)
sortie --env-file /etc/sortie/prod.env WORKFLOW.md

File format:

# /etc/sortie/jira.env
# Comments start with #. Blank lines are ignored.

SORTIE_TRACKER_KIND=jira
SORTIE_TRACKER_ENDPOINT=https://myco.atlassian.net
SORTIE_TRACKER_API_KEY="you@company.com:xpat_abc123def456"
SORTIE_TRACKER_PROJECT=PLATFORM
SORTIE_POLLING_INTERVAL_MS=30000
SORTIE_WORKSPACE_ROOT=~/workspace/sortie

GitHub adapter equivalent:

# /etc/sortie/github.env
SORTIE_TRACKER_KIND=github
SORTIE_TRACKER_API_KEY="ghp_your_personal_access_token"
SORTIE_TRACKER_PROJECT=myorg/myrepo
SORTIE_POLLING_INTERVAL_MS=30000
SORTIE_WORKSPACE_ROOT=~/workspace/sortie

Linear adapter equivalent:

# /etc/sortie/linear.env
SORTIE_TRACKER_KIND=linear
SORTIE_TRACKER_API_KEY="lin_api_your_personal_api_key"
SORTIE_TRACKER_PROJECT=ENG
SORTIE_POLLING_INTERVAL_MS=30000
SORTIE_WORKSPACE_ROOT=~/workspace/sortie

Gitea adapter equivalent:

# /etc/sortie/gitea.env
SORTIE_TRACKER_KIND=gitea
SORTIE_TRACKER_ENDPOINT=https://gitea.example.com
SORTIE_TRACKER_API_KEY="your_gitea_access_token"
SORTIE_TRACKER_PROJECT=sortie-ai/sortie
SORTIE_POLLING_INTERVAL_MS=30000
SORTIE_WORKSPACE_ROOT=~/workspace/sortie

GitLab adapter equivalent:

# /etc/sortie/gitlab.env
SORTIE_TRACKER_KIND=gitlab
# Omit the endpoint on GitLab.com, which the adapter defaults to.
# Set it only to reach a self-managed instance.
SORTIE_TRACKER_ENDPOINT=https://gitlab.example.com
SORTIE_TRACKER_API_KEY="your_gitlab_access_token"
SORTIE_TRACKER_PROJECT=platform/backend/api-gateway
SORTIE_POLLING_INTERVAL_MS=30000
SORTIE_WORKSPACE_ROOT=~/workspace/sortie

Rules:

  • One KEY=VALUE per line. No multiline values.
  • # lines and blank lines are ignored.
  • Optional single or double quotes around values. Outer quotes are stripped, with no escape processing.
  • Only keys starting with SORTIE_ are loaded. All other keys are silently ignored.
  • No variable interpolation within values. $HOME in a .env value is the literal string $HOME.
  • Real environment variables always take precedence over .env values.
  • The .env file is re-read on every WORKFLOW.md reload (file change detection). Real env vars require a process restart to change.

CSV encoding for list fields

active_states and terminal_states accept comma-separated values:

SORTIE_TRACKER_ACTIVE_STATES="To Do,In Progress"
SORTIE_TRACKER_TERMINAL_STATES="Done,Won't Do"

Each item is trimmed of surrounding whitespace. Empty items (from trailing commas or double commas) are discarded. An empty string produces an empty list.

Interaction with $VAR indirection

When a SORTIE_* override is set for a field, it replaces the YAML value entirely. The $VAR expansion that would normally run on the YAML value is skipped for that field. Values from env overrides are literal: $ characters are not expanded.

Example: WORKFLOW.md has api_key: $MY_TOKEN. If SORTIE_TRACKER_API_KEY=tok$5abc is set, the api_key becomes the literal string tok$5abc. The $MY_TOKEN indirection never executes. The $5 is not expanded.

Path fields (workspace.root, db_path) still receive ~ expansion even when set via env overrides. Only $VAR expansion is skipped.


Agent runtime variables

Agent adapters spawn subprocesses that inherit the full parent process environment. Sortie validates none of these variables: they pass straight through, and if one is missing, the agent subprocess fails, not Sortie. COPILOT_HOME is the one Sortie reads for itself, to locate a file the runtime writes.

That inheritance belongs to a local launch. An agent Sortie starts on a remote host through worker.ssh_hosts gets the remote host’s environment, plus the bounded set described under variables carried to a remote agent.

VariableRequired byDescription
ANTHROPIC_API_KEYclaude-code adapter (Anthropic direct)API key for the Anthropic API. The Claude Code CLI reads this on startup. Missing or invalid values cause an authentication error in the agent subprocess.
CLAUDE_CODE_USE_BEDROCKclaude-code adapter (AWS Bedrock)Set to 1 to route Claude Code through AWS Bedrock instead of the direct API.
AWS_ACCESS_KEY_IDclaude-code adapter (AWS Bedrock)AWS access key. Required when CLAUDE_CODE_USE_BEDROCK=1.
AWS_SECRET_ACCESS_KEYclaude-code adapter (AWS Bedrock)AWS secret key. Required when CLAUDE_CODE_USE_BEDROCK=1.
AWS_REGIONclaude-code adapter (AWS Bedrock)AWS region for Bedrock inference. Required when CLAUDE_CODE_USE_BEDROCK=1.
CLAUDE_CODE_USE_VERTEXclaude-code adapter (Google Vertex AI)Set to 1 to route Claude Code through Google Vertex AI.
ANTHROPIC_VERTEX_PROJECT_IDclaude-code adapter (Google Vertex AI)GCP project ID. Required when CLAUDE_CODE_USE_VERTEX=1.
CLOUD_ML_REGIONclaude-code adapter (Google Vertex AI)GCP region. Required when CLAUDE_CODE_USE_VERTEX=1.
ANTHROPIC_BASE_URLclaude-code adapter (proxy)Override the Anthropic API base URL. Use for LiteLLM, custom gateways, or corporate proxies.
COPILOT_GITHUB_TOKENcopilot-cli adapterGitHub token dedicated to Copilot CLI. Highest priority among the three token variables the CLI checks.
GH_TOKENcopilot-cli adapterGitHub token shared with the gh CLI. Second priority for Copilot CLI authentication. Also used by many GitHub tooling integrations.
GITHUB_TOKENcopilot-cli adapterGitHub token common in CI environments. Third priority for Copilot CLI authentication.
COPILOT_HOMEcopilot-cli adapter (optional)Root directory the Copilot CLI writes its per-session state under. Default: ~/.copilot. Sortie reads it too: the adapter resolves <COPILOT_HOME>/session-state/<session id>/events.jsonl, the session-state journal that supplies the run’s token counts. An empty or unset value resolves to the default.
CODEX_API_KEYcodex adapterOpenAI API key. Sortie reads it from its own environment and signs the app-server in with it over the adapter’s protocol channel, on a local launch and a remote one alike, when the runtime reports no account of its own. A local subprocess also inherits the variable; a remote one never receives it. With the variable unset, the adapter falls back to whatever credentials the runtime already holds where it runs, such as ~/.codex/auth.json.
KIRO_API_KEYkiro adapterAPI key the Kiro CLI reads on the headless path. The adapter preflights it at session start (presence plus a usability check), so a missing or invalid credential surfaces as a startup error rather than a hang or a silent empty turn.

A missing ANTHROPIC_API_KEY is the most common claude-code deployment failure. Sortie starts and polls the tracker normally, but every agent session fails at launch with an auth error. The Sortie logs show a worker exit with exit_type=error; the root cause is only visible in the agent’s stderr output.

For copilot-cli, a missing GitHub token is the equivalent failure. The adapter’s preflight check validates that at least one of COPILOT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN is set, or that gh auth status succeeds. If none are available, the session fails to start with agent_not_found. The Copilot CLI itself implements try-and-fallback across these three variables. Precedence matters only when multiple sources hold different valid tokens.

Classic PATs do not work with Copilot CLI

Copilot CLI requires a fine-grained personal access token (prefix github_pat_) with the Copilot Requests permission enabled. Classic PATs (prefix ghp_) fail authentication silently: the CLI falls through all three token variables and reports no valid credential. OAuth tokens (gho_ from copilot auth login) and GitHub App user-to-server tokens (ghu_) also work. If you see authentication failures despite having a token set, check the token prefix.

For codex, a missing CODEX_API_KEY produces the same pattern as Claude Code. Sortie starts normally, but every agent session fails with an authentication error during the app-server initialization handshake. If CODEX_API_KEY is unset, the adapter attempts to use cached credentials from ~/.codex/auth.json; if those are also absent or expired, the session fails to start with response_error. A remote session works the same way: the login travels as a protocol message over the SSH connection, so the key never enters the remote agent’s environment and never needs to be present on the host.

For opencode, authentication is provider-specific and the adapter does not preflight it. OpenCode resolves credentials from its own environment, auth store, project .env, or opencode.json provider config, while the Sortie adapter injects or overrides a small managed OPENCODE_* set on every run and export subprocess.

VariablePurposeDescription
OPENCODE_PERMISSIONPermission policyInline JSON permission policy. When opencode.allowed_tools or opencode.denied_tools is configured, Sortie removes any inherited value and writes its managed policy instead.
OPENCODE_AUTO_SHARESession sharingAuto-share on completion. Sortie-managed runs force this to false.
OPENCODE_DISABLE_AUTOCOMPACTContext compactionManaged by opencode.disable_autocompact.
OPENCODE_DISABLE_AUTOUPDATESelf-updateSortie-managed runs force this to true.
OPENCODE_DISABLE_LSP_DOWNLOADLSP downloadSortie-managed runs force this to true.

In local mode the adapter injects only the managed OPENCODE_* values above; every provider credential, and any OpenCode config-discovery variable such as OPENCODE_CONFIG, comes from the parent environment or from OpenCode’s own auth and config state, unmanaged by Sortie. A remote session receives those same managed values and nothing else of Sortie’s own, so the provider credentials your model selection needs must already exist on the remote host, or be named under worker.ssh_pass_env.

For kiro, authentication is a single credential. The adapter reads KIRO_API_KEY and validates it before the session starts, so a missing or invalid key surfaces as a startup error. That validation runs on a local launch only. A remote session carries KIRO_API_KEY from Sortie’s environment into the agent’s environment on the host, unvalidated, so a bad key there shows up as a failing turn instead. See the Kiro CLI adapter reference for the credential preflight and headless behavior.

For agent-client-protocol, Sortie manages no credential at all. This kind names no default runtime, so there is no fixed variable to preflight or document here: whichever binary agent.command names reads its own credential from the inherited environment, exactly like every other agent adapter’s subprocess. See the Agent Client Protocol adapter reference for the kind itself, and Gemini CLI or Kiro CLI on that route for what each of those two runtimes actually reads.

Variables carried to a remote agent

Every agent kind declares the environment variables its runtime reads as the credential for its default provider. A launch sent to a remote host through worker.ssh_hosts carries that kind’s declared names from Sortie’s own environment, whenever they are set there, without your listing them anywhere. Here is what each built-in kind declares.

Agent kindCarried automatically on a remote launch
claude-codeANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN
copilot-cliCOPILOT_GITHUB_TOKEN, GH_TOKEN, GITHUB_TOKEN
kiroKIRO_API_KEY
codexNone. The login travels as a protocol message instead, so CODEX_API_KEY never reaches the remote agent’s environment.
opencodeNone. Provider credentials are OpenCode’s own to resolve. Sortie’s managed OPENCODE_* settings do reach a remote session, on the same delivery path but under neither worker field’s control.
agent-client-protocolNone. The kind names no default runtime, so it has no fixed credential to declare.

A variable carried this way overrides the value or login the remote host already holds. If Sortie’s own environment sets one of these names for an unrelated purpose, GITHUB_TOKEN for the tracker while your copilot-cli hosts sign in on their own, name it under worker.ssh_disallow_pass_env to keep the host’s login in effect.

Anything else a remote agent needs from Sortie’s environment, a provider key for OpenCode, a registry token a hook cannot supply, goes in worker.ssh_pass_env by name. Both fields take names and never values, and neither affects a local launch. For the precedence rules, the skipped-value warnings, and the remote host’s dd requirement, see that section.


$VAR indirection in WORKFLOW.md

Selected WORKFLOW.md configuration fields resolve environment variable references at startup. This keeps secrets and deployment-specific values out of the workflow file.

$VAR indirection and SORTIE_* configuration overrides are two ways to supply a field from the environment. With indirection, the workflow file names the variable, for example api_key: $SORTIE_GITEA_TOKEN, and Sortie expands it at startup. With an override, a generic SORTIE_TRACKER_* variable such as SORTIE_TRACKER_API_KEY, set in the shell or a .env file, replaces the field value regardless of the workflow file. When both target the same field, the override wins and $VAR indirection is skipped for that field.

Expansion modes

Three expansion modes exist. The mode depends on the field.

Reference only: Expands only when the entire trimmed value is a variable reference ($VAR or ${VAR}). Mixed content like https://example.com/$VAR is returned unchanged, preventing destructive rewriting of URIs and paths.

Anywhere in string: Expands $VAR and ${VAR} references anywhere in the string, including within larger values.

Path: Expands ~ or ~/ at the start of the value to the user’s home directory, then expands $VAR and ${VAR} references anywhere in the rest of the string.

Fields with $VAR support

FieldExpansion modeExample valueResolves to
tracker.endpointReference only$SORTIE_JIRA_ENDPOINThttps://myco.atlassian.net
tracker.api_keyAnywhere in stringuser@example.com:$SORTIE_JIRA_API_KEYuser@example.com:xyztoken123
tracker.projectReference only$SORTIE_JIRA_PROJECTPLATFORM
tracker.query_filterReference only$SORTIE_JIRA_QUERY_FILTERlabels = 'agent-ready'
tracker.handoff_stateReference only$SORTIE_HANDOFF_STATEHuman Review
tracker.no_change_stateReference only$SORTIE_NO_CHANGE_STATEDone
tracker.in_progress_stateReference only$SORTIE_IN_PROGRESS_STATEIn Progress
tracker.api_versionReference only$SORTIE_JIRA_API_VERSION2
workspace.rootPath~/workspace/sortie/home/deploy/workspace/sortie
db_pathPath$SORTIE_DB_DIR/sortie.db/var/lib/sortie/sortie.db

Fields in the core schema outside this table (agent.kind, agent.max_turns, hook scripts, ci_feedback, self_review, reactions, and dispatch) are treated as literal strings with no expansion.

Adapter pass-through blocks (claude-code, worker, github, and similar top-level blocks named after a kind) and each notifications entry are the exception: every string leaf in those blocks is resolved with the same anywhere-in-string semantics, independently of the table above.

The variable names in the table are user-defined conventions, not Sortie-internal identifiers. For the GitHub adapter, common conventions are $SORTIE_GITHUB_TOKEN or $GITHUB_TOKEN for tracker.api_key (a plain personal access token, not email:token format) and $SORTIE_GITHUB_PROJECT for tracker.project (an owner/repo string). See the GitHub adapter reference for per-field semantics.

For the Linear adapter, the conventions are $SORTIE_LINEAR_API_KEY for tracker.api_key (a Linear personal API key carrying the lin_api_ prefix, sent verbatim in the Authorization header with no Bearer prefix; this is the name sortie validate suggests), and $SORTIE_LINEAR_TEAM_KEY for tracker.project (a Linear team key, such as ENG). See the Linear adapter reference for per-field semantics.

For the Gitea adapter, the conventions are $SORTIE_GITEA_TOKEN for tracker.api_key (a Gitea access token, a 40-character hex string with no identifying prefix, sent verbatim in the Authorization: token <key> header with no Bearer prefix, so surrounding whitespace fails authentication; this is the name sortie validate suggests), $SORTIE_GITEA_ENDPOINT for tracker.endpoint (the instance base URL, required because Gitea is self-hosted and has no default host), and $SORTIE_GITEA_PROJECT for tracker.project (an owner/repo string). See the Gitea adapter reference for per-field semantics.

For the GitLab adapter, the conventions are $SORTIE_GITLAB_TOKEN for tracker.api_key (a GitLab access token, sent verbatim in the PRIVATE-TOKEN header, neither Authorization: Bearer nor Authorization: token, so surrounding whitespace fails authentication; the adapter checks neither prefix nor length, because a GitLab administrator can change the access-token prefix through an application setting and a shape check would reject valid tokens on a customized instance; this is the name sortie validate suggests, and it is the tracker credential; do not confuse it with a bare GITLAB_TOKEN, which Sortie does not read), $SORTIE_GITLAB_ENDPOINT for tracker.endpoint (the instance base URL, optional because the adapter defaults to https://gitlab.com, so set it only to reach a self-managed instance), and $SORTIE_GITLAB_PROJECT for tracker.project (the project’s namespace path, which nests to any depth, such as group/project or group/subgroup/project, or its numeric project ID). See the GitLab adapter reference for per-field semantics.

Behavior when a variable is unset or empty

ScenarioBehavior
$VAR resolves to an empty stringThe field is treated as missing. For required fields (e.g., tracker.api_key when the adapter declares it required), this is a startup error.
The referenced variable does not exist in the environmentSame as empty: an undefined variable expands to an empty string.
tracker.handoff_state resolves to emptyStartup error: config: tracker.handoff_state: resolved to empty (check environment variable).
tracker.no_change_state resolves to emptyStartup error: config: tracker.no_change_state: resolved to empty (check environment variable).
db_path resolves to emptyStartup error: config: db_path: resolved to empty (check environment variable).

What this is not

$VAR indirection is not general shell expansion. It does not support:

  • Command substitution ($(command) or `command`)
  • Arithmetic expansion ($((1+2)))
  • Default values (${VAR:-default})
  • Glob expansion (*, ?)

Hook subprocess environment

Hook scripts (after_create, before_run, after_run, before_remove) run as subprocesses with a restricted environment. On POSIX systems, hooks execute via sh -c; on Windows, via cmd.exe /C. The full parent process environment is not inherited.

Injected variables

Sortie injects these variables into every hook invocation. They override any same-named variable from the parent environment.

VariableTypeDescription
SORTIE_ISSUE_IDstringStable tracker-internal issue ID.
SORTIE_ISSUE_IDENTIFIERstringHuman-readable ticket key (e.g., PROJ-123).
SORTIE_WORKSPACEstringAbsolute path to the per-issue workspace directory. Always the same as the hook’s working directory.
SORTIE_ATTEMPTstringCurrent attempt number as a decimal integer. Starts at 1. Increments on retries. 0 if the attempt count is unavailable.
SORTIE_SSH_HOSTstringSSH host allocated for this issue. Present only when SSH mode is active (extensions.worker.ssh_hosts is configured and a host was assigned). Absent in local mode.

after_run hook variables

These variables are injected only during after_run hook invocations.

VariableTypeDescription
SORTIE_SELF_REVIEW_STATUSstringSelf-review outcome for the current run. Values: "disabled" (self-review not configured), "passed" (review passed), "cap_reached" (iteration cap reached without passing), "error" (review loop encountered a fatal error). Set on all after_run invocations.
SORTIE_SELF_REVIEW_SUMMARY_PATHstringAbsolute path to .sortie/review_summary.md in the workspace. Contains a human-readable Markdown summary of the review outcome. Absent when self-review did not run or the summary file was not written.

See Configure self-review for usage examples.

Reaction triage command variables

A reaction’s triage command is not a lifecycle hook, but it runs through the same machinery and so gets the same restricted environment, the same working directory, and the injected variables above. On top of those it receives three of its own.

VariableTypeDescription
SORTIE_REACTION_KINDstringWhich reaction armed: ci, review, bot-review, or merge-conflict.
SORTIE_REACTION_INPUTstringAbsolute path to a JSON file describing the subject. Written before the command starts and removed after it returns.
SORTIE_REACTION_RESULTstringAbsolute path the command writes its answer to. The file does not exist when the command starts.

Both paths sit in a temporary directory created for the run, outside the workspace. See the triage command reference for the two document schemas and the answers the result file accepts.

Inherited variables

Beyond the injected variables above, hooks inherit two categories from the parent Sortie process:

Platform allowlist: A fixed set of standard infrastructure variables, varying by OS:

  • POSIX (Linux, macOS): PATH, HOME, SHELL, TMPDIR, USER, LOGNAME, TERM, LANG, LC_ALL, SSH_AUTH_SOCK, XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS
  • Windows: PATH, SYSTEMROOT, COMSPEC, PATHEXT, USERPROFILE, TEMP, TMP, APPDATA, LOCALAPPDATA, HOMEDRIVE, HOMEPATH, USERNAME

XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS locate the invoking user’s systemd user manager and D-Bus session bus. Both carry no secret; a hook or reaction triage command that starts a systemd user service needs them to reach it. See start a service that outlives a hook.

SORTIE_* prefix: All parent environment variables whose names start with SORTIE_ are inherited. This includes any SORTIE_* variables set via configuration overrides. This is the intended mechanism for passing additional values (API tokens, repository URLs, custom flags) into hooks without exposing the full process environment.

Stripped variables

Everything not in the allowlist and not prefixed with SORTIE_ is stripped. This includes:

  • Cloud credentials: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GOOGLE_APPLICATION_CREDENTIALS
  • API tokens: JIRA_API_TOKEN, ANTHROPIC_API_KEY, GITHUB_TOKEN
  • Application config: DATABASE_URL, REDIS_URL, etc.

This is a security boundary. Hooks run user-authored shell scripts; restricting their environment limits the blast radius of a compromised or buggy hook.

Providing additional values to hooks

Two approaches:

  1. SORTIE_-prefixed variables. Export the value with a SORTIE_ prefix in the parent environment. It passes through automatically.

    export SORTIE_JIRA_API_TOKEN="xyztoken123"
    export SORTIE_REPO_URL="git@github.com:myorg/myrepo.git"
    sortie WORKFLOW.md

    Inside the hook:

    git clone "$SORTIE_REPO_URL" .
  2. In-hook credential loading. Fetch credentials from external sources inside the script.

    source /etc/sortie/hooks-env
    aws sts get-caller-identity

Override precedence

When the same variable name exists in both the parent environment (via SORTIE_* passthrough) and the injected set, the injected value wins. For example, a parent SORTIE_ISSUE_ID=stale is overwritten by the orchestrator’s current SORTIE_ISSUE_ID for the active issue.


MCP server environment

The MCP tool server (sortie mcp-server) runs as a child process of the agent runtime, not of the Sortie orchestrator. The agent runtime builds the MCP server’s environment from the names in the env field of .sortie/mcp.json: a variable not listed in that block does not reach the server. Where the adapter re-expresses the file rather than handing over its path, a listed name can be delivered as a name alone, its value resolved from the agent runtime’s own process environment: see translated delivery. The worker writes per-session context variables and all SORTIE_*-prefixed process environment variables into this block before launching the agent. It writes the file for every agent kind, but the chain runs end to end only where the adapter delivers those servers to its runtime, either directly as the file’s path or re-expressed in the form that runtime parses. Where it delivers neither, nothing spawns the server and the env block reaches nobody; see delivery by agent kind.

Environment composition

The env block is built in two layers:

  1. SORTIE_* process variables (lower precedence). The worker scans the orchestrator’s process environment and collects every variable whose name starts with SORTIE_. This captures credential variables (e.g., SORTIE_TRACKER_API_KEY), configuration overrides (e.g., SORTIE_POLLING_INTERVAL_MS), and any operator-defined SORTIE_* values.

  2. Per-session variables (higher precedence). The worker writes the following variables, overriding any same-named key from layer 1:

VariableTypeDescription
SORTIE_ISSUE_IDstringTracker-internal issue ID. Scopes tool operations to the current issue.
SORTIE_ISSUE_IDENTIFIERstringHuman-readable ticket key (e.g., PROJ-123). Used by tracker_api for project-level scoping.
SORTIE_WORKSPACEstringAbsolute path to the per-issue workspace directory.
SORTIE_DB_PATHstringAbsolute path to the Sortie SQLite database. The MCP server opens this in read-only mode for Tier 1 tools that query run history (e.g., workspace_history). This is the same resolved path that the orchestrator uses. If you set SORTIE_DB_PATH as a configuration override, the MCP server receives that same value.
SORTIE_DISPATCH_IDstringOpaque identifier for the current dispatch, minted fresh for every dispatch, including a retry or a continuation of a resumed session. Used by cost_budget to match the running session’s recorded spend in session_metadata, and by notify_operator to read the matching session id (see below).
SORTIE_SESSION_AGENT_KINDstringDispatch-frozen agent kind for the session (e.g., claude-code). Written unconditionally; may be empty when no agent kind is resolved. Consumed by the notify_operator envelope to record the agent that ran the session.
SORTIE_ATTEMPTstringCurrent retry attempt number as a decimal integer. Written when the orchestrator has attempt information (retries and continuations). Absent on the very first dispatch. Starts at 1 for the first retry and increments on subsequent retries.

The agent’s session identifier is not one of these variables. Sortie writes the tool server’s environment before that identifier is known, so an environment variable would always read empty. Instead, the notify_operator tool reads the session id live, at call time, from a .sortie/dispatch.json record the worker keeps current for the running dispatch, matched against SORTIE_DISPATCH_ID. See notify_operator for how that record is populated and when the session id it reports is empty.

Per-session variables always win. A stale SORTIE_ISSUE_ID in the process environment is overwritten by the orchestrator’s value for the active issue.

Credential delivery

Tier 2 tools (like tracker_api) need tracker API credentials. These reach the MCP server through the env block: the worker’s process environment contains credential variables (e.g., SORTIE_JIRA_API_KEY referenced by tracker.api_key: $SORTIE_JIRA_API_KEY), the SORTIE_* prefix scan collects them, and the worker writes them into .sortie/mcp.json. The MCP server parses the workflow file with the same config loader the orchestrator uses, so its $VAR resolution (see $VAR indirection in WORKFLOW.md) expands references against these variables.

When the operator uses --env-file, the CLI exports the resolved absolute path as SORTIE_ENV_FILE in the process environment. The prefix scan captures this variable, so the MCP server receives the .env file path and applies the overrides in it through the same loader.

The .sortie/mcp.json file is written with 0o600 permissions (owner read/write only) and resides within the per-issue workspace directory. The credential is already available to the agent subprocess, which inherits the full process environment: writing it to the config file does not expand the agent’s access.

Controlled environment

Unlike the hook subprocess environment, which uses a POSIX allowlist plus SORTIE_* prefix filter on the parent process, the MCP server’s environment is the one the env block names. Where an adapter re-expresses the configuration rather than passing its path, a name in that block can be resolved against the agent runtime’s own process environment instead of against a value written into the configuration; see translated delivery. Either way the names come from the env block. Sortie writes no variable outside the SORTIE_* namespace into the configuration and asks for none by name, so a non-SORTIE_* variable of the orchestrator’s process (e.g., PATH, HOME, ANTHROPIC_API_KEY) is not one Sortie hands to the MCP server. The prefix acts as a bounded namespace: no non-Sortie secrets leak into the config file.

Translated delivery and the env block

An adapter that re-expresses the generated configuration rather than passing its path can deliver an env entry by name instead of by value. The codex adapter does: when its own process already holds a variable of that name under the same value, it renders the name into the runtime’s environment-passthrough key and writes no value, and the runtime resolves the value from the app-server’s process environment when it spawns the server. Every other entry is written out with its value.

The reason is the delivery route. That adapter’s configuration travels on the app-server’s command line, and a value written there would sit on an argument list any other user of the host can read. A credential Sortie already holds therefore travels as a name. See the Codex adapter reference for the rendering, and delivery by agent kind for which kinds translate.

Relationship to hook variables

Four per-session variables (SORTIE_ISSUE_ID, SORTIE_ISSUE_IDENTIFIER, SORTIE_WORKSPACE, SORTIE_ATTEMPT) are shared with the hook subprocess environment. SORTIE_DB_PATH, SORTIE_DISPATCH_ID, and SORTIE_SESSION_AGENT_KIND are specific to the MCP execution channel: hooks don’t receive them. In hooks, SORTIE_ATTEMPT is always present (defaulting to 0 on the first dispatch). In the MCP env block, SORTIE_ATTEMPT is written only when the orchestrator has attempt information (retries and continuations); on the very first dispatch it is absent from the per-session set, though it may still appear if the operator’s process environment contains a SORTIE_ATTEMPT variable captured by the SORTIE_* prefix scan.


Install script variables

The install.sh script accepts three environment variables that control installation behavior.

VariableDefaultDescription
SORTIE_VERSIONLatest GitHub releasePin a specific release tag (e.g., 1.19.0). When set, the script skips the GitHub API call to discover the latest version.
SORTIE_INSTALL_DIR/usr/local/bin (root) or ~/.local/bin (non-root)Override the directory where the sortie binary is placed.
SORTIE_NO_VERIFY0Set to 1 to skip SHA-256 checksum verification of the downloaded binary.

Example:

SORTIE_VERSION=1.19.0 SORTIE_INSTALL_DIR=/opt/bin \
  curl -sSL https://get.sortie-ai.com/install.sh | sh

See also

Was this page helpful?