Mert Özen Further With Every Line
Artificial Intelligence OpenAI Codex — #04

The Local Workflow with Codex: Model and Effort, Worktrees, Subagents and Hooks

Mert Özen Nov 15, 2026 12 min 7 views
The Local Workflow with Codex: Model and Effort, Worktrees, Subagents and Hooks

Codex has worktrees and subagents for parallel work, but a worktree opens with only the files Git tracks, and matching hooks start concurrently, not in order. Choosing the model and reasoning effort, side chats and plan mode, the local environment setup script in the desktop app, custom agents that inherit the sandbox, and a hash-trusted PreToolUse hook that protects Bookstore's committed migrations.

Version note: This post describes Codex CLI 0.169.0 (13 November 2026) and the Codex mode in the ChatGPT desktop app; model, effort, session, worktree, local environment, subagent, hook and IDE extension details were checked on 15 November 2026 against the Models, Speed, Developer commands, Worktrees, Local environments, Subagents, Hooks and Codex IDE extension pages at learn.chatgpt.com/docs, and the changes to side chats and the subagent setting against the Codex changelog on the same day. This post is based on the official documentation. The hook script was run with PowerShell 7.7 in a test repo with sample inputs that match the official input schema, and its output was validated against the official output schema; the hooks.json, custom agent and config.toml files were validated against Codex's JSON schema.

Guess First

On Bookstore, the online bookstore used as the shared example across this site's series, Emre started two chats at the same time in two separate worktrees in the desktop app: one would add a CSV export to the order list, the other would update the Angular dependencies. In the second chat npm test failed immediately. The dotnet test runs of the two chats also corrupted each other's data. Emre had also written two PreToolUse hooks: the first logged every file change, the second protected migration files; he assumed the log would always be written before the protection ran.

What is your guess? Why did npm test fail at once in the worktree? Why did the tests of the two worktrees collide? And is the order of the hooks what Emre assumed? The answers are in the worktree and hook sections.

Model and Reasoning Effort

As of 15 November 2026 the Models page gives four recommendations for Codex. GPT-6 Astra is for the hardest end-to-end work. GPT-6.1 Sol is for long and repeated work because it gives near-Astra performance at a lower cost; in Codex its default context window is 500,000 tokens. GPT-6.1 Terra is for writing, visual and computer use work, and GPT-6 Luna for clearly scoped, high-volume work. In Bookstore, Sol for everyday work, Astra for an architecture decision or a hard bug, and Luna for jobs such as summarizing test output or classification are a sensible start.

Reasoning effort decides how much the model thinks about a request: Low in the CLI (Light in the desktop app), Medium, High, Extra High and, on supported models, Max. The documentation's starting advice is the client's default effort for Sol, High for Luna and Light for Astra. It also states explicitly that effort values don't map one to one across model generations. Max gives a single task more thinking time; Ultra splits the work across subagents and runs it in parallel, and only makes sense for work that can be split. Luna goes up to Max and does not support Ultra.

# Inside a session: change the model, or change only the effort
/model gpt-6.1-sol
/model --reasoning=high

# At launch
codex -m gpt-6-astra -c model_reasoning_effort=high

Speed is a separate setting. Fast mode speeds up supported models but uses subscription usage at 2.5 times the Standard rate; in the CLI /fast turns it on and off. We cover the cost side in post seven.

Managing Sessions

In Codex everything is a chat. /new opens a clean chat in the same terminal session; /resume returns to a saved chat and lets you choose the search scope: only this directory, the repo including linked worktrees, or everywhere. /fork copies the current chat under a new ID so you can try another approach. /side opens a short side chat with a transcript separate from the main chat: for asking something like "Is there an obvious risk in this plan?" without interrupting the main work. According to the changelog, features.tui_workspace, introduced with 0.168, shows the side conversation in a pane next to the main chat, and 0.169 added multiple side-chat tabs.

Before starting a change, /plan puts the chat into plan mode; /goal attaches a lasting goal to the chat, such as "finish the migration and keep the tests green". In the desktop app every chat has an integrated terminal bound to its project or worktree (Ctrl+`); because Codex can read that terminal's output, it can bring a running dev server or a failed build into the work.

Bookstore: Order CSV Export

This feature is the same as the one in the ninth post of the Claude Code series (/blog/claude-code-worktree-paralel-session-cloud); a deliberate choice for those who want to compare how the two tools handle the same job. Plan first, then implement:

/plan Add GET /orders/export that returns the signed-in customer's orders as CSV
(OrderId, CreatedAt in UTC ISO 8601, Status, TotalAmount with invariant culture,
Currency). Stream the rows instead of loading all orders into memory, reuse the
existing filters of GET /orders, and add an "Export CSV" button to the My Orders
page in web/bookstore-web. List the files you will touch and the tests you will
add before changing anything.

If the plan shows the files you expect, you approve it and let Codex implement it. The check is still your own run: dotnet test on the API side, npm test in web/bookstore-web on the client side, then /diff and git diff. An unexpected dependency (a new NuGet package for CSV, for example) is where the "ask before adding a package" rule in the root AGENTS.md should kick in.

Working in Parallel with Worktrees

A worktree is a second checkout that shares the same repo's Git metadata. In the desktop app, when you pick Worktree and the starting branch below the composer and send the prompt, Codex opens a worktree under $CODEX_HOME/worktrees and works there in a detached HEAD state. When the work is done you either turn the worktree into a branch with Create branch here, or move the chat and the code to your local checkout with Hand off. Because Git doesn't allow a branch to be checked out in two places at once, the documentation says to use Hand off instead of trying to open the same branch in two places. In the CLI the equivalents are codex --worktree, codex exec --worktree and /worktree inside a session.

The answer to the first question from the guess is here. A worktree starts with only the files Git tracks; node_modules and restored packages are not there. The desktop app's local environments exist for this: a setup script that runs when Codex opens a new worktree, and actions that appear in the top bar. The configuration is stored in the .codex folder at the project root and can be committed to the repo; scripts can be written separately for macOS, Windows and Linux. On Windows the setup script runs in the agent's environment, that is, in PowerShell unless WSL is used. Bookstore's Windows setup script:

dotnet restore Bookstore.slnx
Push-Location web/bookstore-web
npm ci
Pop-Location

For files that Git ignores but a new worktree needs there is .worktreeinclude; Bookstore's secrets live not in the repo but in user-secrets in the user profile, so we don't need that file. The answer to the second question follows from this: because user-secrets live in the profile, every worktree reads the same BookstoreDb connection string and two parallel dotnet test runs change the same database. If you work in parallel, move the tests to a setup that creates its own database, or give each worktree's test run a separate database name with the ConnectionStrings__BookstoreDb environment variable; and pick a separate port per worktree for ng serve. On the disk side, according to the documentation Codex keeps the 15 most recent worktrees it manages by default.

Subagents

In current Codex releases subagents are on by default. Codex starts them when you ask explicitly or when an AGENTS.md or skill instruction asks for it; because each subagent does its own model and tool work, it uses more tokens than a single-agent run. The documentation recommends parallel agents for read-heavy work such as exploration, tests, triage and log analysis; agents editing code at the same time bring conflicts and coordination overhead. Subagents inherit the main session's sandbox and approval settings; a custom agent file cannot change them.

Review the CSV export branch against main with parallel subagents. Use the
ef_query_reviewer agent for the queries in src/Bookstore.Api, one agent for
missing tests, and one for the Angular changes in web/bookstore-web. Wait for
all three, then summarize the findings by category with file references.

Codex comes with built-in agents named default, worker and explorer. You define your own agent with a TOML file under .codex/agents/ (project) or ~/.codex/agents/ (personal); name, description and developer_instructions are required:

name = "ef_query_reviewer"
description = "Read-only reviewer for EF Core queries in src/Bookstore.Api. Use it to check new or changed LINQ queries for N+1 loads, missing AsNoTracking, client-side evaluation and unbounded result sets."
model = "gpt-6.1-sol"
model_reasoning_effort = "high"
developer_instructions = """
You review EF Core code in src/Bookstore.Api. Do not edit files and never run commands that change state.
For every query you look at, report:
- loads inside loops (N+1) and missing Include or projection
- read-only queries without AsNoTracking
- filters or ordering that cannot be translated to SQL
- result sets without paging or Take
Answer with file:line references and one suggested fix per finding. Say "no findings" if there are none.
"""

"Do not edit files" is an instruction, not a boundary; the documentation states explicitly that custom agent instructions don't create a separate read-only sandbox. If you want a hard boundary, start the review in a read-only session. We added max_concurrent_threads_per_session = 4 to the [agents] table in the project file; to turn subagents off entirely you use agents.enabled = false. According to the changelog, since 0.168 Codex warns when features.multi_agent is set explicitly and points to agents.enabled for turning them off. In the CLI /subagents switches between agent threads; for an approval request coming from another thread, the o key opens that thread.

Hooks: Enforcing the Rule

The "never edit an existing migration" rule in AGENTS.md is an instruction. A hook, on the other hand, runs your script at specific points of Codex's lifecycle: SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, Stop, compaction and subagent events. Hooks are read from hooks.json files or from [hooks] tables in config.toml; project hooks load only in a trusted project. Trust has one more step: every hook that is not managed by the organization stays disabled until its definition is approved by hash. A new or changed hook is skipped until it is reviewed and trusted in /hooks.

Bookstore's hook watches apply_patch, Codex's file editing tool. If the *** Update File: or *** Delete File: lines in the patch point at a committed migration, it denies the operation; it leaves new migrations that come with *** Add File: alone. commandWindows is the command used on Windows; following the documentation's advice, the script path is resolved from the Git root, so the hook is found even when Codex is started in a subfolder:

{
  "description": "Bookstore hooks for Codex. Project hooks load only in a trusted project, and each one must be reviewed with /hooks before it runs.",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^apply_patch$",
        "hooks": [
          {
            "type": "command",
            "command": "pwsh -NoProfile -File \"$(git rev-parse --show-toplevel)/.codex/hooks/protect-migrations.ps1\"",
            "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"& (Join-Path (git rev-parse --show-toplevel) '.codex/hooks/protect-migrations.ps1')\"",
            "timeout": 30,
            "statusMessage": "Checking migration files"
          }
        ]
      }
    ]
  }
}
# PreToolUse (apply_patch): blocks patches that change or delete a committed migration file.
# New migration files ("*** Add File:") and migrations that are not committed yet stay editable.
# Written for Windows PowerShell 5.1 and PowerShell 7.
$payload = [Console]::In.ReadToEnd() | ConvertFrom-Json
$patch = [string]$payload.tool_input.command
if (-not $patch) { exit 0 }

$root = git -C $payload.cwd rev-parse --show-toplevel 2>$null
if (-not $root) { exit 0 }
$root = [System.IO.Path]::GetFullPath($root).TrimEnd('\', '/')

$blocked = @()
foreach ($line in $patch -split "`r?`n") {
    if ($line -notmatch '^\*\*\* (Update|Delete) File: (.+)$') { continue }

    # The patch may use a path relative to the session directory or an absolute path.
    $path = $Matches[2].Trim()
    if (-not [System.IO.Path]::IsPathRooted($path)) { $path = Join-Path $payload.cwd $path }
    $full = [System.IO.Path]::GetFullPath($path)
    if (-not $full.StartsWith($root, [System.StringComparison]::OrdinalIgnoreCase)) { continue }

    $relative = $full.Substring($root.Length).TrimStart('\', '/').Replace('\', '/')
    if (-not $relative.StartsWith('src/Bookstore.Api/Data/Migrations/', [System.StringComparison]::OrdinalIgnoreCase)) { continue }

    git -C $root ls-files --error-unmatch -- $relative *> $null
    if ($LASTEXITCODE -eq 0) { $blocked += (Split-Path -Leaf $full) }
}

if ($blocked.Count -eq 0) { exit 0 }

[ordered]@{
    hookSpecificOutput = [ordered]@{
        hookEventName            = 'PreToolUse'
        permissionDecision       = 'deny'
        permissionDecisionReason = "Committed migration files cannot be changed: $($blocked -join ', '). Add a new migration with the add-migration skill."
    }
} | ConvertTo-Json -Depth 5 -Compress
exit 0

The answer to the third question from the guess is in the first paragraph of the documentation: matching command hooks for the same event are launched concurrently, and one cannot prevent another from starting. There is no order between the log hook and the protection hook; if one needs the other's result, combine them into a single script. It is the same in Claude Code, where matching hooks run in parallel (post seven of that series). The differences specific to Codex are elsewhere: handlers of type prompt and agent are parsed and skipped, the "ask" decision is not yet supported in PreToolUse, and hosted tools such as web search don't go through hooks. The documentation describes tool hooks as a useful guardrail, not a complete security boundary. Because the .codex folder is a protected path in the sandbox, Codex cannot change its hook by itself.

The Same Workflow in the IDE Extension

The same work can be done from Visual Studio Code. The extension can add open files, the selected code and recent chats to the composer, shows the summary and diff of the changes next to the code, and lets you hand a long job over to the cloud. Configuration, the AGENTS.md chain, skills, MCP servers and hooks are the same as in the CLI; the two also share the stored sign-in. On Windows, if you want the agent to run inside WSL 2, set chatgpt.runCodexInWindowsSubsystemForLinux to true in the VS Code settings. A general comparison of AI assistants in the IDE is in the ninth post of the IDEs series.

In Production

Parallel work has a cost. Every worktree is a separate checkout with separate packages and build output; every subagent means separate model calls. Run read-heavy work in parallel, gather the edits in a single chat and read every subagent result yourself before merging.

Review hooks like code. A committed hook runs commands with its own permissions on the machine of everyone on the team. Hash-based trust sends a changed hook back to approval; --dangerously-bypass-hook-trust is only for automation that already vets hook sources elsewhere.

Common Pitfalls

The same database and port across worktrees. Because user-secrets live in the profile, every worktree reads the same connection string. Separate the test database and the dev server port per worktree.

Merging subagent results without reading them. The main chat's summary is a short version of what each subagent did; open the thread where you have doubts.

Assuming hooks run in order. Matching hooks start concurrently; resolve dependencies between them in a single script.

Thinking higher effort is always better. Max and Ultra take longer and use more; according to the documentation most tasks don't need them.

A Small Experiment

★☆☆ Ask for the same small job in two separate chats with /model --reasoning=low and --reasoning=high. Note the time, the change in usage in /status and the difference in the result.

★★☆ Write a setup script for a worktree in the desktop app, then start two parallel chats and run each one's tests with a separate database name. Did the collision go away?

★★★ Adapt Bookstore's hook to your repo, trust it with /hooks and ask Codex to edit a committed migration. Then change a single line in the script and see the hook drop back to needing approval.

All of this happens on your computer; when your laptop shuts down, the work stops. In post five we move the same jobs to Codex Cloud: reusable cloud environments, setting up .NET and Angular, parallel tasks for the same job and turning the result into a pull request.