@ambionframework/codex runs an Ambion seat on codex app-server. Use an
executor holds the install, the shared
options, the wiring, the sign-in rules, and the shared troubleshooting. This
page holds what differs for Codex. The README holds the
positioning.
A seat that Codex runs. codex() defines the executor of an agent.
codexExecution() gives a room or a runtime the services that run it. Codex
owns the model loop and its thread. The host calls the tools of the seat
through the room.
The kernel gains no dependency. @ambionframework/ambion imports no model
library. @openai/codex belongs to this package only, pinned to the exact
version 0.160.1. The package brings the codex binary for the platform, and
the executor finds it from @openai/codex/package.json. Set codexPath on
codexExecution() to run another executable.
Sign in with one of two ways.
- Set
CODEX_API_KEYin the environment of the process. The executor logs in with the key before it opens a thread, and Codex keeps the key in memory. Codex writes noauth.json. The app-server does not read the variable. - Run
codex loginonce. The seats link the sign-in file that the command writes,~/.codex/auth.json. Sign in with ChatGPT to run on a ChatGPT Plus or Pro subscription. A host with no browser runscodex login --device-auth.
A key wins over auth.json. A seat with CODEX_API_KEY ignores the
linked file, as codex exec does. For a subscription, leave CODEX_API_KEY
out of the environment. A login that Codex refuses is a permanent failure. Its
message names the cause and never holds the key. A login with no answer in 30 seconds is a transient failure. Codex reports no
cost, so the executor records none.
The seats of an execution share a Codex home of their own. The default
home is ~/.ambion/codex, under the HOME of the host. The execution sets
CODEX_HOME to it for each run of the binary, and the CODEX_HOME of the
host never reaches the binary. A seat reads no config.toml and no
AGENTS.md from ~/.codex, and it starts none of the MCP servers that
file names. The home persists, so a thread survives a restart of the host.
The execution creates it with the mode 0700, because it holds full
transcripts, logs, and the linked login.
The home links the login of the host. The first activation creates the
symbolic link auth.json in the home. It points to auth.json in the
CODEX_HOME of the host when that variable is set, else to
~/.codex/auth.json. The host and all seats then share one login, because
Codex rotates the refresh token in place. A HOME that env sets moves the
default. The login option names another file.
- A home that already holds an
auth.jsonkeeps it. The execution never replaces or edits that file. - A host with no login file gets no link. A seat on
CODEX_API_KEYneeds none.login: falselinks nothing in any case. - Where the host refuses symbolic links, such as Windows with no privilege, the execution makes a hard link. If both fail, the activation fails as permanent. The message names both paths.
- Several activations can start at once. A link that another activation made counts as success.
A keyring login cannot be shared. With cli_auth_credentials_store
set to keyring, or to auto on a host with a keyring, Codex stores the
login in the keyring under a key that holds a hash of the CODEX_HOME path.
A seat home has another path, so it finds no login. Set the store to file
in ~/.codex/config.toml and run codex login again. Or sign in to the
home: CODEX_HOME=~/.ambion/codex codex login.
import {
createRuntime,
defineAgent,
definePerson,
isSaid,
startRoom,
} from '@ambionframework/ambion';
import { codex, codexExecution } from '@ambionframework/codex';
import { memoryJournals } from '@ambionframework/journal';
const planner = defineAgent({
name: 'planner',
identity: 'Reads the plan and names what is missing.',
executor: codex({
instructions: 'Speak when the plan lacks evidence.',
model: 'gpt-5.6-luna',
modelReasoningEffort: 'medium',
}),
});
const priya = definePerson({ name: 'priya', identity: 'Project manager.' });
const runtime = createRuntime({ storage: memoryJournals() });
const room = await startRoom({
runtime,
name: 'delivery',
agents: [planner],
execution: codexExecution(),
});
const visit = await room.visit(priya);
const exchange = await visit.send({ text: 'Is the plan ready?' });
const messages = await exchange.waitForClose();
console.log(messages.filter(isSaid).map((message) => message.text));
await room.stop();A seat gets files and a shell from a workspace. Give the seat the tools
of a workspace in bundles (Workspace).
The tools run behind the workspace port, so the backend is a choice of the
host: in memory here, a directory, or a workstation.
import { defineAgent } from '@ambionframework/ambion';
import { codex } from '@ambionframework/codex';
import { memoryBackend } from '@ambionframework/just-bash';
import { openWorkspace } from '@ambionframework/workspace';
const workspace = openWorkspace({ name: 'site', backend: { bash: memoryBackend() } });
const writer = defineAgent({
name: 'writer',
identity: 'Writes the notes of the site.',
executor: codex({
instructions: 'Keep the notes in your workspace, and say where they are.',
model: 'gpt-5.6-luna',
bundles: [workspace.tools()],
}),
});codex(options) takes the shared options
and three settings of the model. The executor passes each setting to
codex app-server.
| Option | Required | Default | What it does |
|---|---|---|---|
model |
Yes | None | A Codex model identifier |
modelReasoningEffort |
No | Codex default | minimal, low, medium, high, xhigh, max, ultra, or persistent, as the model lists them |
reasoningSummary |
No | 'auto' |
auto, concise, detailed, or none: see Debug an activation |
The executor fixes the policy of the thread. A seat has no option for
it. The executor sets sandbox to read-only, approvalPolicy to
never, and cwd to an empty temporary directory. The trust boundary states
why.
codexExecution(options) takes the options of the executable.
| Option | Default | What it does |
|---|---|---|
codexPath |
The bundled binary | A codex executable to run |
env |
None | Variables to lay over the allowlisted variables of process.env. undefined removes one |
home |
.ambion/codex under the HOME of the host |
The Codex home of every seat |
login |
The auth.json of the host |
The auth.json to link into the home. false links nothing |
The defaults of home and login read the host. They use the HOME
and the CODEX_HOME of process.env with env laid over them. Neither value
reaches the binary.
The binary runs with an allowlisted environment. The base is the
variables of process.env that pass: PATH, USER, LOGNAME, TMPDIR,
TEMP, TMP, TZ, LANG, USERPROFILE, APPDATA, LOCALAPPDATA,
SYSTEMROOT, COMSPEC, PATHEXT, HTTP_PROXY, HTTPS_PROXY, ALL_PROXY,
NO_PROXY, http_proxy, https_proxy, all_proxy, no_proxy,
NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, SSL_CERT_DIR, CODEX_API_KEY,
CODEX_ACCESS_TOKEN, and CODEX_CA_CERTIFICATE. Windows compares these
names without case. Two prefixes pass, OPENAI_ and LC_. The env option lays over the base: a
value adds or replaces a variable, and undefined removes one. Then the
execution sets the variables of the seat, and they win over env. Every run
of the binary gets the same environment, the codex debug models run for the
catalog included.
Every other variable stays with the host. SHELL and TERM are not on
the list, because a seat has no native tool and no terminal. Other CODEX_
variables, such as CODEX_SQLITE_HOME and CODEX_SANDBOX, move the state,
the sandbox, or a server of Codex out of the seat. A cloud key, a token of a
code host, and the socket of an SSH agent reach no process. A provider with
another env_key in the config.toml of home needs that variable in
env. Pass OPENAI_API_KEY: undefined and CODEX_API_KEY: undefined in
env to keep both from the codex process, and sign in with codex login.
The seat sets CODEX_HOME, HOME, and USERPROFILE. CODEX_HOME is
home. HOME and USERPROFILE are the private directory home/home, which
the execution creates with the mode 0700. Codex finds skills under
$HOME/.agents/skills, so a skill of the host user reaches no prompt and no
dotfile of the host user is in the reach of the binary. The login still
works, because auth.json lives in CODEX_HOME.
The room tools are dynamic tools of the thread. The room tools (say,
schedule, seat, unseat, dismiss, recall) and the tools of the
agent live in the host. The executor lists each one in the dynamicTools
parameter of thread/start, with its description and its JSON Schema. No
server runs beside the process, and no socket or bridge exists. The call goes
to the host, which calls the room, and the room checks it, so no approval
mode is needed.
flowchart LR
H[host] -- JSON-RPC on stdio --> C[codex app-server]
C -- item/tool/call --> H
H --> T[room tools]
T --> R[room commit]
- The model calls a tool. Codex sends the notification
item/startedwith an item of the typedynamicToolCall. - Codex sends the request
item/tool/callto the host, with the call id, the tool name, and the arguments. - The executor runs the call on the tool that the core bound. It gives the
tool the step id that
steps.idOf(turnId, callId)returns for the pass and the call. It answers withcontentItemsandsuccess. - Codex sends
item/completedand gives the answer to the model.
An answer holds text and images. A text part becomes an inputText
item. An image part becomes an inputImage item with a data URL. success
is false when the tool reports an error or throws. The model then reads
the message as the tool output.
The host refuses every other request of the server. Codex can ask for
an approval, a user input, or a token refresh. The executor answers each
with the error code -32601 and keeps a warning notice that names the
method. approvalPolicy is never, so a seat does not see the first two.
A thread keeps its tools. Codex writes the dynamic tools in the first
line of the rollout file of the thread. thread/resume takes no tool list.
Exchange continuity states the rule that follows.
How an activation runs states the read position.
One process serves one activation. The executor starts
codex app-server with the config flags of the seat, sends initialize
and the initialized notification, and opens a thread. The first pass sends
turn/start with the view. A later pass sends the delta as the next
turn/start of the same thread. Codex calls the object that turn/start
creates a turn. One pass runs one Codex turn, and this page says pass. The
process stays up between passes and stops at close.
The thread parameters carry the seat's part. thread/start takes
baseInstructions, the seat text: the note of the executor, the mechanism,
and the agent instructions, in that order, with a blank line between them.
The core fixes all three for an activation, so the executor builds the text
once. baseInstructions replaces the base prompt of Codex, which teaches
apply_patch and the shell. A seat has no native tools and needs nothing from
that prompt. The text arrives unchanged.
The echo of the input is the signal that the model read it. Each
turn/start and turn/steer carries a clientUserMessageId. When Codex
sends item/completed for the userMessage item with that clientId, the
executor calls read with the range of the view or the delta. A tool result
reaches the model when the tool returns, so the executor calls delivered at
once.
A pass ends on turn/completed. The status tells whether the pass
completed, failed, or was interrupted. A failed status carries a
codexErrorInfo that the classifier reads.
Codex takes a steer. The executor sends turn/steer with the turnId of
the pass as expectedTurnId. Codex echoes the line as a userMessage item,
the executor calls read, and the core records consumed: true. A line that
arrives before turn/start answers waits for the answer. A line that arrives
after the pass ended waits for the next delta, and the core records
consumed: false. Codex refuses turn/steer with the error code -32600 when
the pass is already over. The line is not read, and the next pass carries it.
A cut interrupts the pass. The signal of the activation makes the
executor send turn/interrupt. The pass settles when turn/completed
arrives, or after a grace period of 5 seconds. A cut is no failure.
close stops the process. It ends the input of the process and sends
SIGTERM. A process that is still alive after 2 seconds gets SIGKILL. A host
that dies takes codex app-server with it: the OS closes the pipe, and the
process ends.
Traces holds the step kinds. The table below gives the Codex source of each step.
Each item that Codex reports becomes steps in the trace. Codex streams the
text of an item in deltas and sends the whole item at item/completed. The
executor maps each delta to a step and the completed item to a closing step.
| Codex item or notification | Steps |
|---|---|
agentMessage |
text deltas, then a closing text |
reasoning |
thinking deltas, then a closing thinking; a new summary part starts after a blank line |
dynamicToolCall |
tool_call and tool_result; a room tool has its own name |
warning |
notice at level warning, with the message of Codex, up to 2000 characters |
configWarning |
notice at level warning, with the summary and the details |
deprecationNotice |
notice at level warning |
error with willRetry |
notice at level warning, such as "Reconnecting... 1/5" |
| Any other item | notice at level warning that names the item type, when the item completes |
thread/start or thread/resume |
Once for each thread: session, and a notice at level info with the thread, the home, and the rollout file |
thread/tokenUsage/updated |
usage |
turn/completed with failed |
No step; the failure goes to the end step |
The session step opens the activation. The executor records it after
the thread opens. It holds the name codex, the version of the binary, the
model, the working directory, the thread id, the sign-in kind that
account/read reports (none when Codex reports no account), the
permission mode approvalPolicy, sandbox, the names of the bound tools, and
the MCP servers that mcpServerStatus/list reports.
The id of a step is unique in the room. A real codex numbers the items
of each pass from item_0. The id of a step holds the activation id, the
turnId, and the item id, as in message:3:gpt:1:turn-1:item_1. A room
tool takes that id as the key of its commit, so the say of each activation
lands under its own key.
A tool call shows with its name. A tool with a namespace shows as
namespace__tool, and a room tool with its plain name. The core raises no
tool event for a room tool that commits an entry; see
The room tools. A compose call adds the
approval step and the nested steps with a parent. A failed tool call
carries the text of the answer, or "The tool failed.". An image of the answer
returns to its form in the trace: the bytes of a data URL, or the URL.
An item of another type shows that a native tool exists. A seat has no
native tool, so Codex reports only the items above. A command, a file
change, a web search, or a plan item means that a newer codex added a tool
that the recipe does not turn off. The trace keeps a warning notice:
"Codex reported a native item. A seat has no native tools." The
executor maps nothing else from such an item.
The trust boundary states the guard. A notice never
gates the activation, and the trace keeps a repeated warning once for each
text.
One usage step follows each model request. thread/tokenUsage/updated
reports the tokens of the thread. Its last field holds the request that
just ended, and the executor reads that one. The activation reports the
sum on activation_end. Codex reports no cost, so the cost field stays
absent. A room that budgets on cost needs its own price table.
Input tokens include the cache. The count is
input = inputTokens - cachedInputTokens - cacheWriteInputTokens
cacheRead = cachedInputTokens
cacheWrite = cacheWriteInputTokens
output = outputTokens
The three parts add up to inputTokens. A count that subtracts only the
cached tokens counts the cache writes twice. The count ignores
reasoningOutputTokens. The recorded runs do not show whether outputTokens
includes them.
The trace shows each step of the model. Give the logger option of
createRuntime a function that keeps the steps. The end step holds the
failure of a pass that failed.
The reasoning summary feeds thinking. Codex shows no raw reasoning. It
shows a summary that the model writes when the request asks for one. The
catalog of some models turns the summary off, so the executor asks for it:
reasoningSummary is auto by default and goes to Codex as
model_reasoning_summary. concise and detailed set the size. none
asks for no summary, and the request then has no reasoning.summary field.
The default trace policy cuts each thinking block to 280 characters. Set
defineAgent({ trace: { thinking: 'full', toolOutput: 'full' } }) to keep all
of each block (The policy).
One notice joins the trace to the full record of Codex. The notice
has the text "Codex thread" and the level info. Its data holds the
thread id, the home of the seat, and the rollout path from
thread.path. The rollout is the file
<home>/sessions/YYYY/MM/DD/rollout-<time>-<thread>.jsonl. It holds the
instructions, every input and output item, the reasoning, and the tool calls.
Each thread has one notice for the activation. logs_2.sqlite in the home
holds the log of the codex process.
Failure classification states the shared rule and the text set. The text set holds the words of the Codex login.
A failed pass carries a typed error. turn/completed with the status
failed holds codexErrorInfo. When the error names an HTTP status in
httpStatusCode, the shared status rule applies to it. Otherwise the shared
text set reads the message. A text that names a full context window or a
spent output limit reports stop: 'length', which is no failure.
A process that exits during a pass is a transient failure. The failure
message holds the reason (exited with code 1 or was stopped by SIGKILL)
and the last 2000 characters of the stderr of the process. The room retries
the activation.
A missing codex binary is permanent when the executor looks for it.
The executor reads the model catalog from the binary. It throws
PermanentError when the platform has no binary, or when @openai/codex
is not installed and no codexPath is set. A codexPath that names no file
is transient: the message reads "could not start".
A refused initialize or thread/start fails the pass. The error of
the server is the message of the failure.
Exchange continuity states the rule, the recorded session, and the fresh start.
An activation resumes the thread that spec.resume names. The release
records the thread id from the answer of thread/start. An activation whose
spec.resume names a Codex thread sends thread/resume with the id. Every
other activation starts a fresh thread.
A resume needs the same tools. The rollout file of the thread keeps the
dynamic tools of its start, and thread/resume takes no new list. The
executor compares the tools in the first line of the rollout file with the
bound tools. When they are equal, it resumes. When they differ, it starts a
fresh thread.
A resume that Codex cannot honor starts a fresh thread. An error of
thread/resume, such as an unknown thread id, makes the executor start a
fresh thread, run the same prompt, and record the new id. The trace keeps a
notice at level info with the text "Codex thread not resumed". A dead
process is an ordinary failure, and the executor does not start a fresh
thread for it.
A resume reads no history and counts no earlier usage. The executor sends
excludeTurns: true with thread/resume, so Codex hydrates no history and
sends no deprecationNotice. Right after thread/resume answers, Codex
repeats thread/tokenUsage/updated for the last pass of the earlier
activation. The executor drops a usage note when no pass runs or when its
turnId differs from the running pass.
A resumed thread takes the seat text of its own activation.
thread/resume takes baseInstructions, so the thread follows the new text
and drops the old text. The first prompt of a resumed activation holds the
view alone, as the first prompt of a fresh thread does.
Threads live in the Codex store of the seat home. Codex persists threads
under sessions in the home, ~/.ambion/codex/sessions by default. A host
that loses that directory falls back to a fresh thread. Give home a
directory that persists. Codex refuses to create its helper binaries under a
temporary directory.
A seat has no native tools, ever. Files and a shell come only from the
workspace tools, behind the workspace port, whether the workspace is in
memory, a directory, or a remote workstation. The seat has the room tools and
the tools of tools and bundles. No option gives a seat the shell, the file
edits, the web search, or the sandbox of Codex. The workstation backend needs
no separate guard against a native command.
Trust compares the three kinds.
Code Mode ignores the sandbox. Code Mode is a JavaScript runtime that the
model calls through exec and wait. Its JavaScript can read host files on
a read-only sandbox, as seen on 0.155.1, so sandboxMode and permission
profiles do not confine it. A feature flag cannot turn it off on 0.160.1,
because the model catalog turns it on.
The recipe therefore patches the catalog. The header of src/catalog.ts holds
the evidence.
The recipe replaces the catalog entry. A custom catalog overrides the
entry of a model. The executor runs codex debug models on the installed
binary once for each binary in the process, patches the entry of the seat
model, writes the catalog to a temporary directory, and passes the path as
model_catalog_json. src/catalog.ts holds the exact fields, the features,
and the reason for each. The recipe has five parts:
- The catalog entry. The patch removes Code Mode, the patch tool, the
search tool, and the usage instructions of apps, plugins, and skills. It
leaves
input_modalitiesandsupports_image_detail_originalas the entry has them. - The features. The config sets 37 features to
false, among themshell_tool,unified_exec,code_mode,apps,plugins,computer_use,multi_agent,hooks, andview_image. This removes the shell, the tools that a feature adds, and the features that act on the host or the network, such asshell_snapshot.memoriesis off by default, and the entry keeps theconfig.tomlof the seat home from turning it on. Codex turnsunified_execon again unless a managed requirement pins it, and it adds no tool whileshell_toolis off (tested on 0.158.0). Features that stay on by default give no tool, andcodex features listprints removed flags, such assteerandsqlite, as on (tested on 0.158.0). A removed flag has no effect. - The tool switches and the skills.
web_searchis'disabled'.update_planandexperimental_request_user_inputare disabled.skills.include_instructionsandskills.bundled.enabledarefalse, so noskills_instructionsmessage reaches the model and Codex installs no system skill in the seat home. - The bundled REPL. The config declares
mcp_servers.node_replas disabled, by name. Without it the tool list holdsmcp__node_repl.js. - Defense in depth. The thread runs on a read-only sandbox, with no network, no approval, and an empty temporary directory as its working directory. No repository and no host file is the default context.
The recipe turns off the traffic and the state that a seat does not
need. check_for_update_on_startup, analytics.enabled, and
feedback.enabled are false. Only the terminal interface of Codex reads
check_for_update_on_startup, and codex app-server starts no update check.
The config sets the key so that a later version keeps the same behavior.
memories.generate_memories, memories.use_memories, and the memories
feature are false. With a dummy API key, a run makes no request except the
model requests and opens no outbound connection. The binary tier asserts it.
A seat on a ChatGPT sign-in is the case that the keys protect, and the binary
tier does not run it. The temporary directory of an activation holds the
patched catalog and the empty working directory. The executor removes it when
the activation closes, and at process exit.
A model with no catalog entry does not start. The activation fails as
permanent, and the message names the model. The executor never leaves native
tools on by accident. Use a model that codex debug models lists.
The version pin guards the recipe. The feature names, the config keys,
and the catalog fields belong to the pinned @openai/codex version. A newer
codex can add a native tool that the recipe does not turn off, and it can
drop a key that the recipe sets. Codex then warns about the key. The binary
tier asserts that a default seat produces no warning notice and no skills
block, so such a change fails a test. Trust a new version only when the
binary tier and the live exclusivity test pass on it. The last troubleshooting
entry holds the procedure.
An image from a tool of the seat reaches the model. A tool that returns
an image part, such as the workspace read of a picture, sends the image to
a model that reads images. Codex puts it in the tool output as an
input_image, and the trace keeps the part. No native tool reads an image,
because the view_image feature is off. A model with no image input in its
catalog entry stays text-only, and Codex replaces the image with a text
placeholder.
The seat home keeps the host config out, and the host owns the seat
config. The execution points CODEX_HOME at the seat home, so the
~/.codex of the host user changes no seat: its model_provider reroutes no
request, its mcp_servers start no process, and its AGENTS.md joins no
prompt. The private HOME does the same for the skills of the host user. The
config.toml of the seat home is the responsibility of the host. The
executor overrides each config key that the recipe names. Any other key in
that file survives by a deep merge. An extra [mcp_servers.<name>] table
starts a server whose tools reach the host, and a features.<name> = true
entry turns on a feature that the recipe does not list. The default home
~/.ambion/codex holds no config.toml until a person writes one. Write only
keys that you trust.
A linked login is shared with the host. The seat home holds a link to the
login file of the host. A seat has no native tool, so no tool of the seat
reads or writes the seat home. The workspace tools reach the workspace
only. Set login: false and CODEX_API_KEY to give a seat no login file.
Three tiers test the package.
| Tier | What runs | What it proves |
|---|---|---|
| Fake | A fake codex app-server through the connect option, over a real journal |
The executor over the JSON-RPC protocol, with no process |
| Binary | The bundled codex app-server, with a local endpoint that plays the model |
The recipe, the tool list, the thread, and the traffic |
| Live | The bundled binary on a real model | The executor suite, and the exclusivity of tools with a real model |
The binary tier cannot prove that a model obeys: a scripted model does what
the script says, and it streams no deltas. Set AMBION_LIVE_DUMP=<dir> to
write one JSON file for each live case, with the room calls, every step, and
the prompt and the request of each pass.
Run the live tier in one of two modes. Every definition sets the model
gpt-5.6-luna and modelReasoningEffort: 'medium'. The mode key runs when
CODEX_API_KEY is set and pays the API account. The mode login runs when
codex login wrote auth.json and pays the ChatGPT subscription. The key
wins when the host has both, and a file skips when the host has neither.
CODEX_API_KEY=... pnpm --filter @ambionframework/codex run test:live
pnpm --filter @ambionframework/codex run test:live # on the loginUnset CODEX_API_KEY to run on the login.
A live run costs money: run one file with
pnpm --filter @ambionframework/codex exec vitest run --config vitest.live.config.ts test/live/exclusive.test.ts.
The shared table lists the symptoms that hold for every kind. These rows are specific to Codex.
| Symptom | Cause and fix |
|---|---|
| The seat answers in its final message and nobody hears it | Codex has its own final-answer channel. The room hears only say. The note at the start of the seat text says so. A seat that still ends with plain text shows a text step and no tool_call in its trace, and activation_end reports said: false. |
| The process does not start | The activation fails as transient, with "could not start" or "exited with code N" and the stderr of the process. Check that codexPath names an executable file, or that @openai/codex is installed for the platform. Run <codex> app-server in a terminal to see its error. |
| A thread cannot resume | The executor starts a fresh thread, and the seat loses what the old thread held. The record still holds every line. Look for a missing sessions directory in the seat home, a changed home option, a codex of another version, a change of the bound tools (the notice "Codex thread not resumed" says it), or another account than the one that started the thread. |
| A run fails with a sign-in message | The failure is permanent. Set CODEX_API_KEY, or run codex login. A HOME that env sets moves the default login. A login in the OS keyring is invisible to a seat: see Sign in. |
| A seat starts with no login although the host has one | The host login file is <CODEX_HOME>/auth.json when the environment of the host sets CODEX_HOME, else ~/.codex/auth.json. Check that path, or pass login. |
Cannot link .../auth.json to the login file ... |
The activation could make neither a symbolic link nor a hard link, and the failure is permanent. Run CODEX_HOME=<home> codex login to sign in to the home, or pass login: false and set CODEX_API_KEY. |
| A native tool shows up after a Codex upgrade | The seat lists or calls a tool that is not a room tool and not one of yours, and the trace holds a warning notice that names an item type. A newer codex added a feature or a catalog field that the recipe does not cover. Run the binary tier (test/binary.test.ts), which asserts the exact tool list and no warning notice for a default seat, and run test/live/exclusive.test.ts to see the name. Compare codex debug models and codex features list of the new version with src/catalog.ts. Check each feature that is on by default in the new version against the comment of EXCLUSIVE_FEATURES in src/catalog.ts. Add the feature or the field, and keep the version pinned until both tests pass. A warning that names a config key means that the new version dropped the key: remove it from exclusiveConfig. |