Guidance for Codex working in this repository.
ccb — a public, solo-maintained Codex derivative, originating from the Anthropic npm sourcemap leak (v2.1.88, 2026-03-31) and subsequently reorganised into a packages-based monorepo. Repo is public on GitHub; binaries are distributed via GitHub Releases (install.sh / install.ps1). "Solo-maintained" means one maintainer (Icarus603) — not "private repo". Each install runs locally for its operator (no server-side multi-tenant), and there is no public npm package.
The repo is post-V7 refactor: monolithic src/ is gone, all code lives in packages/* and packages/@ant/* workspaces. Test count drifts week-to-week (see bun test output for the current number); the invariant is 0 fail.
bun install # install + wire git hooks
bun run dev # dev mode (MACRO defines + STABLE_FEATURES injected)
bun run dev:inspect # dev mode w/ debugger (set BUN_INSPECT=9229)
bun run build # release build → dist/cli.js (single file, target=bun)
bun run build:standalone # standalone binary for current platform
bun run build:platforms # cross-compile binaries for all platforms
bun test # all unit + integration tests
bun run smoke # tests/smoke/ — runtime probe + plugin hooks
bun test <path> # single file
bun run lint # biome lint .
bun run lint:fix # biome lint --fix .
bun run format # biome format --write .
bun run health # one-shot: lint + tests + build + verifier subset
bun run doctor:arch # full architecture verifier suite (~80 rules)
bun run check:unused # knip — find unused exports
bun run release v26.5.N # tag + push; release.yml builds + publishes binariesThe user's ~/.local/bin/ contains three look-alike binaries. Knowing which
is which matters when debugging — testing the wrong one wastes a session.
| Command | Symlink target | Purpose |
|---|---|---|
ccb |
~/.local/share/ccb/versions/<vX.Y.Z> binary |
Released ccb — frozen at last bun run release. Auto-updates from GitHub Releases. Stable snapshot, no local edits visible. |
ccbdev |
→ <repo>/dist/cli.js (live symlink) |
Local-build ccb — runs whatever bun run build last produced. Reflects every code change the moment build finishes. |
Codex |
Anthropic's official CLI (separate npm install) | Upstream Codex from Anthropic. Used as reference / sanity comparison; unrelated to this repo. |
Verifying a code change: always use ccbdev after bun run build. ccb
will show stale behaviour because it's pinned to the last released version.
Restart matters: ccbdev reads dist/cli.js at process start. A running
session keeps the OLD bytecode in memory — rebuild + restart, not just
rebuild. (The 2026-04-29 bash-mode-debugging session lost ~30min to a
stale ccbdev process making one fix look like it didn't take.)
which/symlink check (paste this if you forget):
ls -la ~/.local/bin/ccb ~/.local/bin/ccbdev ~/.local/bin/Codex- Runtime: Bun ≥ 1.3 (not Node).
bun:bundle,bun:ffi,Bun.embeddedFiles,globalThis.Bun.$are all in use. - Build:
build.tscallsBun.build({ target: 'bun', splitting: false }). Entry:packages/cli/src/entry/cli.tsx. Output: singledist/cli.js(~13 MB). The artifact is Bun-only —node dist/cli.jswill throw on the firstBun.$reference. - Defines:
scripts/defines.ts(centralized).MACRO.VERSIONis derived from the latest reachable git tag — never hardcoded. - Module system: ESM (
"type": "module"), TSX withreact-jsx. - Monorepo: Bun workspaces —
packages/*andpackages/@ant/*resolved viaworkspace:*. - Lint/Format: Biome 2.x (
biome.json). - Distribution: Standalone binaries via
bun build --compile, installed to~/.local/share/ccb/versions/<version>and symlinked from~/.local/bin/ccb. Auto-update lives in the binary itself. Not on npm.
packages/cli/src/entry/cli.tsx— true entrypoint.main()dispatches fast paths (version, mcp, bridge, daemon, ps/logs/attach, dump-system-prompt, etc.) before falling through tomain.tsx.packages/cli/src/entry/main.tsx— Commander.js CLI definition. Subcommands:mcp,server,ssh,open,auth,plugin,agents,auto-mode,doctor,update, etc. The default.action()handler dispatches REPL vs headless viamode-dispatch.ts.packages/cli/src/entry/mode-dispatch.ts— owns the REPL-launch /--print/--continue/--resume/ setup branches. CallsprocessSessionStartHooks,loadPluginHooks, MCP prefetch.packages/app-host/src/init.ts— one-shot init (enableConfigs(), env var application, OAuth populate, telemetry).
packages/agent/query.ts— turn-loop generator (the central API call function). Drives streaming response, tool dispatch, stop-hook handling.packages/agent/QueryEngine.ts— higher-level orchestrator wrappingquery(). Owns conversation state, compaction, file-history snapshots, attribution.packages/repl/src/screens/REPLView.tsx— interactive REPL screen (Ink/React). Input handling, message display, permission prompts, keyboard shortcuts.packages/agent/core/AgentLoop.ts— the headless-pmode loop (used bybun run dev -pand SDK).
packages/provider/src/— provider abstraction.claudeLegacyRuntime.ts— main streaming path; callsanthropic.beta.messages.create.anthropic/client.ts— Anthropic SDK client construction.openai/,gemini/,grok/,codex/— alt-provider adapters (each translates Anthropic-shape requests/responses to its own SDK).cyberRiskInstruction.ts— security-work authorization injected into the system prompt.connections.ts— connection registry (single source of truth for provider config; env vars are fallback only).
- Provider selection in
packages/provider/src/providers.ts.
packages/tool-registry/src/tools/— 56 tool directories. Each:<ToolName>/<ToolName>.ts(x)(definition +call()), often with a UI component. Examples: BashTool, FileReadTool, FileEditTool, GrepTool, AgentTool, SkillTool, McpAuthTool, EnterPlanModeTool, etc.packages/tool-registry/src/tools/registry/— tool assembly + feature-gated inclusion.packages/tool-registry/src/services/toolExecution.ts— execution dispatcher (permission checks → call → result mapping).
packages/@ant/ink/— forked Ink (custom reconciler, hooks, virtual-list rendering).packages/repl/src/components/— REPL React components (170+).REPLView.tsx,Messages.tsx,MessageRow.tsx,PromptInput/,Settings/, etc.
packages/output/— non-REPL output rendering (headless--printmode).- React Compiler runtime (
react/compiler-runtime) — decompiled output has_c()memoization calls.
packages/app-host/src/state/AppState.tsx— central app state context.packages/app-host/src/state/AppStateStore.ts— store factory + defaults.packages/app-host/src/state/store.ts— Zustand-style store.packages/app-host/src/bootstrap/state.ts— module-level singletons (session ID, CWD, project root, registered hooks, token counts, model overrides, permission mode).- Selectors —
state/selectors.ts,state/sessionSelectors.ts,state/permissionSelectors.ts,state/mcpSelectors.ts, etc.
The codebase uses a ports-and-adapters pattern. Inner packages declare contracts; outer packages (app-host, cli) install bindings at startup.
packages/app-host/src/runtime/installPluginBindings.ts— wires@Codex/config/plugin/_deps.tssetters to real implementations.packages/app-host/src/runtime/bootstrap.ts— installs runtime skeleton bindings (logging, fs, plugins).packages/agent/host.ts—installAgentHostBindings()/getAgentHostBindings()— the agent package consumes hooks/messages/UI APIs through this.packages/agent/agentHostBindings.ts— concrete binding factory;executeStopHooks,executeTaskCompletedHooks, etc. all wire here.- Only one
_deps.tsleft:packages/config/plugin/_deps.ts(cross-boundary plugin loader). Other packages migrated to direct imports + host bindings (V7 P7.1).
packages/bridge/— Remote Control / Bridge mode (feature-gatedBRIDGE_MODE). CLI:ccb remote-control/ccb rc/ccb bridge.packages/daemon/— long-running supervisor (feature-gatedDAEMON).- Background sessions —
ccb ps/logs/attach/kill/--bg(feature-gatedBG_SESSIONS).
packages/agent/context.ts— assembles env info, git status, AGENTS.md hierarchy.packages/agent/prompts.ts—getSystemPrompt()builds the static + dynamic prompt sections (default vs proactive paths).packages/storage/src/claudemd.ts— discovers and loads AGENTS.md from project hierarchy.packages/config/outputStyles.ts— output style definitions (Default / Explanatory / Learning + user-defined.md).packages/agent/coordinatorMode.ts— coordinator-mode prompt (replaces default whenCLAUDE_CODE_COORDINATOR_MODE=1).
- SSOT:
scripts/default-features.ts(STABLE_FEATURESarray). Bothdev.tsandbuild.tsimport from here so dev and release stay aligned. - Per-run override:
FEATURE_<NAME>=1 bun run dev. - In code:
import { feature } from 'bun:bundle'thenfeature('FLAG_NAME')returns boolean. - Type: declared in
packages/cli/src/types/internal-modules.d.ts. - Full registry: see
docs/feature-flags.md(84 flags, categorised stable / opt-in / platform-detect). - Common stable flags (currently default-on):
BRIDGE_MODE,CHICAGO_MCP,VOICE_MODE,TOKEN_BUDGET,TEMPLATES,COORDINATOR_MODE,MCP_SKILLS,TRANSCRIPT_CLASSIFIER. - Don't redefine
featurelocally — always import frombun:bundle.
| Area | Status | Path |
|---|---|---|
| Computer Use (macOS + Win) | Restored, working | packages/@ant/computer-use-{mcp,input,swift} |
| Chrome Native Host | Restored | packages/@ant/Codex-for-chrome-mcp |
| Voice Mode (Push-to-Talk) | Restored, requires Anthropic OAuth | packages/voice/ |
| OpenAI compat (Ollama/DeepSeek/vLLM) | Restored | packages/provider/src/openai/ |
| Gemini compat | Restored | packages/provider/src/gemini/ |
| Grok compat | Restored | packages/provider/src/grok/ |
| Plugins / Marketplace | Restored | packages/config/plugin/, packages/repl/src/components/plugin/ |
| MCP OAuth | Working | packages/mcp-runtime/src/auth.ts |
| audio-capture-napi, image-processor-napi, color-diff-napi | Implemented | packages/*-napi/ |
| ripgrep-napi | Implemented (rust + napi-rs, in-process ripgrep) | packages/ripgrep-napi/ |
| modifiers-napi | Working — bun:ffi Carbon shim, not real NAPI (name is historical) |
packages/modifiers-napi/ |
| url-handler-napi | Stub | packages/url-handler-napi/ |
| Analytics / GrowthBook / Sentry | Empty implementations | various |
| LSP Server (host side) | Removed | — |
OpenAI (CLAUDE_CODE_USE_OPENAI=1):
- Stream adapter mode — Anthropic-shape requests get translated to OpenAI Chat Completions; SSE response gets translated back to
BetaRawMessageStreamEvent. Downstream code is provider-agnostic. - Env:
OPENAI_API_KEY,OPENAI_BASE_URL,OPENAI_MODEL,OPENAI_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL.
Gemini (CLAUDE_CODE_USE_GEMINI=1):
- Independent env namespace — does not share with OpenAI/Anthropic.
- Env:
GEMINI_API_KEY,GEMINI_BASE_URL(defaulthttps://generativelanguage.googleapis.com/v1beta),GEMINI_MODEL,GEMINI_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL.
Grok — packages/provider/src/grok/. Similar adapter pattern.
- Framework:
bun:test(built-in assertions + mocks). - Layout: tests live in
packages/<pkg>/src/**/__tests__/<name>.test.ts(in-tree) ortests/{integration,smoke,unit}/. - Smoke:
bun run smoke— runtime probe + live-fire plugin hooks (validates host bindings, hook dispatch, real binary boot). - Mock pattern:
mock.module()+await import()— must be inlined per test file (cannot be hoisted to a shared helper). - Current state: 0 fail invariant — see
scripts/health-check.tsfor the file split (packages/vstests/). Total counts drift; don't pin them in docs.
- Run:
bun run doctor:arch— ~80 rules covering owner-over-shim, encapsulation, ratchets, host-binding completeness, silent-failure detection, feature-flag boundaries, file-size LOC budgets, model-id boundary, etc. - Pre-commit hook:
.githooks/pre-commitruns the fast subset (~8 rules, <2s). Wired bybun install(thepreparescript setscore.hooksPath). - Pre-push hook: full
doctor:arch+ smoke tests. See.githooks/pre-push. - Bypass:
PRE_COMMIT_SKIP=1 git commit ...only when you're certain the rule mis-flags. Don't use--no-verify. - Ratchets: many rules count something (LOC, tsc errors, cycles, coupling, silent-failure findings) and lock the current value. They can shrink, not grow. To bump downward: do the work, run the verifier with
--tighten, commit the new constant.
verify-deps-setters-wired— every_deps.tssetter slot must be wired by someinstallXxxBindings()(P7.1 lock).verify-host-binding-completeness— every host binding contract method has a wire.verify-silent-failure-ratchet— 12-audit suite (scripts/audit-silent-failures/); current baseline ≈ 818 findings, all CRITICAL=0 / HIGH=0.verify-file-size— grandfathered LOC ratchet (scripts/file-size-baseline.json); new files ≤ 800 LOC.verify-tsc-errors— decompilation tsc-error budget (current ~3300, ratchet down only).verify-feature-canonical—feature()calls must come frombun:bundleimport, not redefined.verify-no-packed-modelid-leak— packed<connId>:<modelId>must be unpacked at user-facing boundaries (system prompt, /context, /config, error messages). Vouch tags// modelid:bare-by-construction|already-unpacked|alias-only|debug-onlyfor the rare exception. History: 69f3c7c8 fixed two /config leaks but did NOT sweep — same-class leaks resurfaced in prompts.ts and analyzeContext.ts a week later (254615e2).verify-tighten-monotonic— meta-verifier: every ratchet's--tightenis one-way down. Caught yoloClassifier 1497→1509 regression in 739c83e1; fixed 7 ratchets in one sweep.
Tag scheme: v<year>.<month>.<Nth-of-month> CalVer.
v26.4.1 ← Apr 2026, 1st release
v26.4.13 ← Apr 2026, 13th release
v26.5.1 ← May 2026, counter resets
- Strict 3-segment numeric → semver tools (
isVersionNewer, npm semver, GitHub Release sort) all work natively. 26.4.99 < 26.5.1holds correctly.- One month rarely exceeds tens of releases — patch segment never overflows.
- Visually distinct from ant's
v2.1.NNNseries. - Don't put identifier strings (
carus, etc.) in version numbers — breaks sort. Identifiers go in--versionoutput / banner / README. - Historical
v1.carus.NNN(001 ~ 009) retained as history.v26.x.x > v1.x.xnumerically, so auto-update naturally moves users forward.
Release flow — strict order matters. release.ts v2 only tags ci.yml-verified SHAs:
# 1. commit (pre-commit hook: lint + fast doctor:arch ~8 rules)
git commit -m "..."
# 2. push (pre-push hook: full doctor:arch + smoke tests)
git push origin main
# 3. wait for ci.yml on the new SHA (release.ts requires HEAD === origin/main, but
# won't gate on ci.yml status — be a good citizen and verify it's green first)
gh run watch <run-id> --exit-status
# 4. tag — release.ts pre-flight refuses if HEAD ≠ origin/main
bun run release v26.5.34What bun run release does (see scripts/release.ts):
- Pre-flight: clean working tree, valid CalVer tag, branch=main (
--force-branchto override), HEAD === origin/main, no stale draft releases. - Warns if the previous
release.ymlrun failed or is still in-progress. - Creates an annotated tag at HEAD, pushes it.
release.ymlthen builds binaries for darwin-{arm64,x64} / linux-{arm64,x64} / win-x64, generates SHA256 sidecars, publishes to GitHub Releases. MACRO.VERSIONis derived from the tag (scripts/defines.ts) — no manual version bump anywhere.
Two GH Actions pipelines, two roles:
ci.yml— fires on everypushtomain. Runs lint / build / tests / doctor:arch / smoke. Gates whether a SHA is releasable.release.yml— fires on tag push (v*.*.*). Builds + publishes platform binaries. Doesn't re-run tests.
Auto-update: built into the binary (packages/updater/src/). Polls GitHub Releases, downloads new platform binary, atomically swaps the version symlink.
- Don't try to fix all tsc errors — many come from decompilation (
unknown/never/{}types). They don't affect Bun runtime. The ratchet locks the count from growing. - Build artifact is Bun-only —
node dist/cli.jsdoes not work (Bun-native APIs in the bundle). Run viabun dist/cli.jsor the release binary. bun:bundleimport — Bun built-in module, resolved by the runtime/builder. Don't replace with a custom function.- MACRO defines — only edit
scripts/defines.ts.MACRO.VERSIONcomes from git tag. - Feature flags default-off — every
feature('X')returnsfalseunless added toSTABLE_FEATURESinscripts/default-features.tsor set per-run viaFEATURE_X=1. - React Compiler output — decompiled
const $ = _c(N)memoization is normal, don't "clean it up". - Biome config — many lint rules disabled (decompiled code isn't strict-lint-friendly).
.tsxfiles: 120-col width + required semicolons; everything else: 80-col + as-needed. - No
src/directory — V7 refactor is complete. All code is underpackages/<pkg>/. Inter-package imports use the@Codex/<pkg>path; intra-package imports stay relative. - Plugin hooks —
loadPluginHooks()writes topackages/config/plugin/_deps.tsplaceholders, whichinstallPluginBindings.tswires through toapp-hostSTATE. Plugin hooks and registered callbacks live in the sameSTATE.registeredHooksslot. Seedocs/feature-flags.mdandpackages/agent/hooks.ts. - Solo-maintained, operator-trusted —
CYBER_RISK_INSTRUCTION(packages/provider/src/cyberRiskInstruction.ts) authorizes full security work for the operator. The binary runs locally for whoever installed it; don't add gatekeeping for hypothetical multi-tenant / hosted-service concerns. The repo is public, but solo-maintained — design decisions belong to the maintainer, not imagined downstream users (seefeedback_no_imagined_users.md).
The V9 type-narrowing pass (commits a6f851da → 3d3f07e4, ~10 commits, tsc 3209 → 3103) discovered four patterns worth carrying forward:
- Shadow-stub bugs are P0: setter-injected forwarder modules like
_deps.tsaccumulate hand-typed local stubs that drift from the real impl elsewhere.: unknownaccepts any literal at construction sites, so the bug never surfaces — until a caller passes more args than the stub takes (V9-2.6isDuplicatePath), or the return value differs (V9-2.7coerceDescriptionToStringreturns''instead ofnull, breaking?? fallback), or the value disagrees with the canonical (V9-2.8BUILTIN_MARKETPLACE_NAME = 'anthropics'vs canonical'builtin'). When auditing such files, grep everyexport function NAMEfor OTHERexport function NAMEin the repo; each match is a candidate broken stub. Compare signature/return/value against the canonical by hand. Memory:feedback_p0_bug_chains_in_setter_injected_repos.md,feedback_shadow_stubs_compound_with_dead_code_cleanup.md. - ACCESS-vs-CONSTRUCTION rule: when narrowing a
: unknownshim viaimport typere-export, only ACCESS-pattern callers (x.field/x.method()) yield TS error elimination. CONSTRUCTION-pattern callers (: T = {…}) produce ZERO yield because: unknownalready accepts every literal. Triage bygrepbefore changing: ≥3 access sites = worth narrowing; type-annotation-only = skip. Memory:feedback_only_access_pattern_unknown_shims_yield.md. - Three strategies for
unknownshims: (a) re-export from canonical home (V9-3TaskStateBase, V9-2cToolUseContext/Message), (b) delete if dead (V9-2/2.5 11 dead pairs), (c) build the canonical type FROM the construction site when no separate canonical home exists (V9-2b realBashProgress/PowerShellProgress/MCPProgressextracted from each tool'sonProgress({ data: {...} })). Iter-18's "no canonical home → not feasible" verdict was wrong; iter-19 retracted it. bun run scripts/verify-build-resolves.tsafter every shim deletion: a "0-caller" delete that breaks build is a shadow stub being silently used, not dead code. Caught a bug in V9-2.7 immediately; would have looked like "the whole batch is bad" if batched at the end.- When to STOP V9-style work: V9-2d (
cli/headless.ts5 slots) and V9-4 (~60 misc slots) are documented indocs/refactor/v9-deps-shrinkpath.mdas either NOT FEASIBLE or DEFERRED. The 95-shim ecosystem has been triaged; remaining yield per shim is ≤5 errors and most fall into CONSTRUCTION/no-caller categories. Don't restart V9-style mining without a specific shim identified as ACCESS-pattern with concrete yield estimate. The "Don't try to fix all tsc errors" rule above is the binding upper guideline.
- Slow startup:
packages/app-host/src/init.ts,packages/cli/src/setup/setup.ts. - Stop hook didn't fire:
packages/agent/internal/stopHooksCore.ts→packages/agent/agentHostBindings.ts:executeStopHooks→packages/agent/hooks.ts:executeStopHooks→hasHookForEvent→getRegisteredHooks(). - Tool didn't run:
packages/tool-registry/src/services/toolExecution.ts(permission gate + dispatch). - MCP server didn't connect:
packages/mcp-runtime/src/clientRuntime.ts,useManageMCPConnections.ts. - Provider routing:
packages/provider/src/providers.ts:resolveConnectionForModel. - Plugin not loaded:
packages/config/plugin/pluginLoader.ts:loadAllPluginsCacheOnly,loadPluginHooks.ts. - AGENTS.md not picked up:
packages/storage/src/claudemd.ts. - Feature flag not taking effect: check
scripts/default-features.ts:STABLE_FEATURES(build/dev) andFEATURE_X=1env var (per-run). - V9 territory (next major refactor pass — type narrowing through
import type):docs/refactor/v9-deps-shrinkpath.mdis the launch blueprint. Five sections cover §1 the last_deps.tslazy-require (✅ V9-1 landed 2026-05-04), §2 8 unknown setter slots + 3 LSP/MCP getter pairs (✅ V9-2 + V9-2.5 landed 2026-05-04 — both turned out to be pure dead code, not type-narrowing candidates), §3 TaskState double-union, §4+§5 the 95-slot= unknownboundary-shim ecosystem. Each phase has an atomic-rollback assessment.