All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Agent-first release driven by mining a 584-call, 15-day real-usage dataset (see docs/dogfooding-real-usage.md). Eleven issues closed across three themes: make agent tool-selection unambiguous (#92), make the audit signal clean enough to mine again (#93 + #94), and formalize the cross-session handoff pattern agents organically converged on (#91). The rest — small bug fixes, soak findings, and the first two Layer 2 tools from the seedRoadmap — fell into the same cycle once the milestone was trimmed to match.
Test count: 1269 → 1325 (+56). No breaking behavior changes; MCP tool shapes unchanged.
- Cross-session handoff protocol (
#91):codex_context(CLI + MCP) now renders a top banner whencontext.next_sessionexists, with relative age (3h ago,2d ago) and a[likely stale — Nd]marker past 7 days. The key is omitted from the entries list below the banner to avoid duplicating content. Tier-independent — appears even attier:'essential'.DEFAULT_LLM_INSTRUCTIONSgained a nudge so agents learn to write the key at session end. No new MCP tool; the convention works with existingcodex_set. - Non-interactive password resolution (
#88):askPasswordnow resolves from--password-file <path>(explicit flag onset --encrypt,get --decrypt,run --decrypt,edit --decrypt; refuses world-readable files) and from theCCLI_PASSWORDenv var (ambient fallback with a one-shot stderr warning). Interactive TTY prompt remains the fallback. Unblocks CI, cron, and script workflows for encrypted entries. ccli lint --seed-quality(#82): new heuristic pass onccli lintthat flags low-amplification entries (too short with no project-specific signal, matching a known low-amp phrase pattern, or carrying an interpolation landmine). Soft warnings ("consider rewriting"), exemptingcommands.*,deps.*,session.*,project.*,context.next_session, andconventions.seedDensity. Operationalizes the seed-density principle.ccli topology(#83): co-occurrence analysis on the audit log. Surfaces which entries get pulled in the same focused-read sessions and which sit isolated. Signal source iscodex_getwith a specific key; bootstraps (codex_context), searches (codex_find), and misses are deliberately excluded.--dotemits graphviz for visualization;--jsonfor pipelines;--period,--limit,--min-sessionsfor filtering. Alias-canonical (reads via an alias and via the resolved key count as the same entry).
- MCP tool descriptions rewritten for agent tool-selection clarity (
#92): all 19 MCP tool descriptions now explicitly disambiguate overlapping pairs. The core behavioral lever:codex_getno longer advertises "list all entries" — that affordance was driving a 48% empty-key rate in the dataset (agents usingcodex_getwhencodex_contextwas the intended tool). Added "prefer X over Y when…" hints forcodex_contextvscodex_getvscodex_find,codex_setvscodex_alias_set,codex_removevscodex_alias_remove,codex_copyvscodex_renamevscodex_alias_set,codex_config_*vscodex_*, andcodex_statsvscodex_audit. Documentation-only — no behavior change. - CI:
softprops/action-gh-releasev2 → v3 (#86): last remaining Node-20 action in.github/workflows/release.yml. Gets ahead of the 2026-06-02 forced-Node-24 cutover and the 2026-09-16 Node 20 removal from GitHub Actions runners.
codex_removeand siblings logop:"remove", notop:"write"(#93part 1):classifyOproutescodex_remove,codex_alias_remove,codex_confirm_remove, andcodex_resetto a new distinct'remove'op category. Stats output shows removes as a separate count;readWriteRatiois now pure (reads / true writes) instead of being inflated ~15% by conflated removes.codex_audit --writes-onlycorrespondingly narrows to true writes only.aliasResolvedreliably captured on CLIcodex_run(#93part 2): the CLI callsite now pre-resolves the target key and passes bothrawKey(user input) andkey(resolved) to the instrumentation wrapper, matching the convention already used byset,get, andrename. Closes the dominant gap surfaced by the dataset (aliassgdiecaptured on only 3 of 6 runs).codex_copy'saliasResolvedtracks source, not dest (#94): both MCP and CLI wrappers now resolve the source key (the thing that might be an alias) rather than the dest (a new canonical key). Auditkeysemantic preserved — the operation target is still dest.bootstrapRateandwriteBackRatelabels clarify MCP-sessions-only (#93part 3): stats output now says "of MCP sessions" instead of the ambiguous "of sessions" — CLI one-shot sessions can't bootstrap, and lumping them in overstated overall discipline.data importrejects shape-invalid sections instead of silently dropping them (#89): section extraction was split from the shape check; a present-but-malformed section (e.g.aliases: "not-an-object") now aborts the import with a typed error rather than silently coercing toundefinedand leaving the other sections to apply. Closes a loophole in the#77transactional-import guarantee.- Error hints use
getBinaryName()instead of hardcodedccli(#90):import_max_bytesrejection message (CLI + MCP) and thehandleProjectFilealready-exists error no longer point users to a command they may not have installed under a renamed distribution (e.g.ccli-beta). - Interpolation syntax landmines rewritten (
#39):files.interpolateandcontext.execInterpolationcontained literal${key}/$(key)references that would fail interpolation. Backslash-escaped each occurrence; post-fix all 77 stored entries interpolate cleanly. Display still renders the unescaped syntax (interpolate resolves the escape before returning).
Stable promotion of v1.12.2-beta.1 after successful soak. Beta.0 shipped the consolidated export/import integrity patch (five audit findings from the 2026-04-09 review); beta.1 added one fix found during beta.0 flogging (#87 _meta stamping on import). No source-code changes since the beta.1 tag — soak passed clean. See beta entries below for full detail; consolidated summary follows.
- Export integrity envelope: CLI
data exportand MCPcodex_exportnow wrap output in a$codexclienvelope carrying version, type, scope,exportedAt,includesEncryptedflag, and asha256of the payload. Imports verify the hash (tamper detection) and surfaceincludesEncryptedin the confirmation prompt / preview. Bare-shape files (pre-v1.12.2 exports, hand-written JSON) still import via the backwards-compat path. Closes #78.
data export allanddata import allshare a file shape: default is one wrapped file that round-trips cleanly;--splitpreserves the legacy three-file layout. Closes #76.- Transactional multi-section imports:
data import all(CLI + MCP) validates every section up front and commits all sections in a singlesaveAllcycle. A validation failure in any section rolls back the entire import. Closes #77. - Leaf value-type validation:
validateImportEntriesnow rejects non-string leaves (numbers, booleans, arrays,null) with a clear error listing the offending keys. Closes #79. - Import size cap: CLI and MCP imports reject payloads larger than
import_max_bytes(default 50 MB) before reading them. Override viaccli config set import_max_bytes <bytes>. Closes #80. - Imported entries no longer land
[untracked]:saveAllstamps_metafor new/changed leaves on import so a fresh backup restore doesn't surface every entry as the highest-suspicion staleness tier. Unchanged leaves (under--merge) keep their existing timestamps._metaentries for leaves no longer present are dropped. Closes #87. - Auto-backup timestamp:
createAutoBackupnow includes milliseconds in its directory names so back-to-back calls in the same second no longer collide withmkdirSync EEXIST.
Second prerelease of v1.12.2. Adds one bug fix found during beta.0 soak flogging; all beta.0 changes carry forward unchanged.
- Imported entries no longer land
[untracked]:saveAllnow stamps_metafor new/changed leaves on import so a fresh backup restore doesn't surface every entry as the highest-suspicion staleness tier. Unchanged leaves (under--merge) keep their existing timestamps._metaentries for leaves no longer present are dropped. Closes #87.
First prerelease of v1.12.2 — the consolidated export/import integrity patch. Five audit findings from the 2026-04-09 review, batched into one beta for soak testing. Install via brew install seabearDEV/ccli/ccli-beta for side-by-side testing with stable.
- Export integrity envelope: CLI
data exportand MCPcodex_exportnow wrap output in a$codexclienvelope carrying version, type, scope,exportedAttimestamp,includesEncryptedflag, and asha256hash of the payload. Imports verify the hash (tamper detection), surfaceincludesEncryptedin the confirmation prompt / preview, and warn on future version. Bare-shape files (pre-v1.12.2 exports, hand-written JSON) still import via the backwards-compat path. Closes #78.
data export allanddata import allnow share a file shape:ccli data export all -o backup.jsonpreviously wrote three suffixed files (backup-entries.json,backup-aliases.json,backup-confirm.json) that the single-filedata import allcouldn't consume. Default now produces one wrapped file containing all three sections that round-trips cleanly. Pass--splitfor the legacy three-file layout. Closes #76.- Transactional multi-section imports:
data import all(CLI + MCP) now validates every section up front and commits all sections in a singlesaveAllcycle via the newsaveAllstore primitive. Previously, a validation failure in the aliases section AFTER entries had already been saved left the store half-applied, and process death between section writes had the same effect. Closes #77. - Leaf value-type validation:
validateImportEntriesnow rejects non-string leaves (numbers, booleans, arrays,null) with a clear error listing the offending keys. Previously these slipped through structural validation and surfaced as confusing errors in downstream read paths. Closes #79. - Import size cap: CLI and MCP imports now reject payloads larger than
import_max_bytes(default 50 MB) before reading them, so a misplaced heap dump or adversarial input can't OOM the process with a cryptic V8 error. Override viaccli config set import_max_bytes <bytes>. Closes #80. - Auto-backup timestamp:
createAutoBackupnow includes milliseconds in its directory names so back-to-back calls in the same second no longer collide withmkdirSync EEXIST.
Main surfaces to flog:
- Export-all roundtrip:
ccli-beta data export all -o backup.json && ccli-beta data reset all --force && ccli-beta data import all backup.json --force— store should be identical before and after. Try with entries, aliases, and--confirm-marked commands all present. --splitcompat:ccli-beta data export all -o split.json --splitstill producessplit-entries.json/split-aliases.json/split-confirm.jsonfor workflows that depend on per-section files.- Envelope integrity: hand-edit an exported
backup.json(change a value, add a key), thenccli-beta data import all backup.json --force— expect a clear sha256-mismatch error, no store mutation. - Encrypted roundtrip:
ccli-beta set api.key secret --encrypt, then export with--include-encrypted, reset, reimport,ccli-beta get api.key --decrypt— should return the original plaintext. Without--include-encrypted, the export contains[encrypted]placeholders and a subsequent import must be rejected with a clear error. - Backwards compat: any pre-v1.12.2 export file (bare
{entries, aliases, confirm}shape, no$codexclienvelope) should still import cleanly. - Size cap: create a >50 MB garbage JSON (
yes | head -c 60M > huge.json), attemptccli-beta data import entries huge.json— expect a clear pre-read rejection namingimport_max_bytes. - Transactionality: a manually-shaped import with valid entries but a malformed aliases section (e.g. non-string value) must leave entries unchanged. Pre-fix, entries would have been written before aliases validation tripped.
Patch release covering two HIGH-severity export/import integrity bugs uncovered in the 2026-04-09 audit.
- Auto-backup now honors project scope:
createAutoBackuppreviously only copied the global store (~/.codexcli/), soccli data importandccli data resetinside a project with.codexcli/had no rollback point — the destructive op proceeded against the project store with nothing backed up. Backups now go to<projectRoot>/.codexcli.backups/for project scope, and the destructive op aborts if the backup can't be written. Closes #74. - Encrypted values survive export/import roundtrip: exports ran every encrypted leaf through
maskEncryptedValues, emitting the literal string[encrypted]. Re-importing that file silently overwrote real ciphertext in the store with the placeholder, destroying every encrypted entry. Masking on export is still the default (safe for sharing); opt into real ciphertext with--include-encrypted(CLI) orincludeEncrypted: true(MCPcodex_export).validateImportEntriesnow rejects any import containing the[encrypted]sentinel with a clear error naming the offending keys. Closes #75.
Stable promotion of v1.12.0-beta.0 after successful soak (2026-04-11 weekend → 2026-04-16). No source-code changes since the beta tag — soak passed clean. See the beta.0 entry below for full detail; the consolidated summary follows.
ccli audit --follow/-f: Live audit log streaming. Tailsaudit.jsonlwith the same colored format as snapshot mode and supports all existing filters (--writes,--key,--src,--mcp,--cli,--project,--hits,--misses,--redundant,--detailed).--jsonemits NDJSON. Closes #41.- LLM bootstrap nudge:
DEFAULT_LLM_INSTRUCTIONSnow tells agents to rungh issue list --state openaftercodex_contextat session start, so in-flight work is cross-referenced before coding begins. Closes #68.
- Telemetry tail cache:
loadTelemetry()caches parsed entries and reads only new tail bytes on subsequent calls, mirroring theloadAuditLog()pattern from v1.11.1. Eliminates full file re-read on everycomputeStats()call. Closes #81. - Audit query:
queryAuditLog()reads cached audit entries directly instead of allocating a defensive.slice()copy on every query.
First prerelease of v1.12.0 — perf + observability mini-release. Install via brew install seabearDEV/ccli/ccli-beta for side-by-side testing with stable.
ccli audit --follow/-f: Live audit log streaming. Tailsaudit.jsonland formats new entries with the same colors and layout as snapshot mode. Supports all existing filters (--writes,--key,--src,--mcp,--cli,--project,--hits,--misses,--redundant,--detailed).--jsonemits one JSON line per entry (NDJSON). Closes #41.- LLM instructions: Agents are now nudged to run
gh issue list --state openaftercodex_contextat session start to cross-reference in-flight work. Closes #68.
- Telemetry tail cache:
loadTelemetry()now caches parsed entries and reads only new tail bytes on subsequent calls, mirroring theloadAuditLog()pattern from v1.11.1. Eliminates full file re-read on everycomputeStats()call. Closes #81. - Audit query optimization:
queryAuditLog()now reads the cached audit entries directly instead of creating a defensive.slice()copy, avoiding an O(N) allocation on every query.
ccli audit --followis the main new feature to exercise. Try it with filters:ccli-beta audit -f --writes,ccli-beta audit -f --mcp,ccli-beta audit -f --json.- In one terminal run
ccli-beta audit -f, in another runccli-beta set test.flog "hello"— verify the entry appears formatted in real time. - Ctrl+C should exit cleanly with no dangling watchers.
Stable promotion of v1.11.1-beta.8 after successful soak. Consolidates all beta-cycle fixes below. See individual beta entries for commit-level detail.
codex_importdefaults tomerge:true. Callers that relied on replace-by-default must passmerge:falseexplicitly.codex_importparameter renamed:json→data, accepts either a JSON string or an object literal.typedefaults to'entries'.
- MCP server freeze (3 independent causes):
- Object.prototype poisoning — telemetry entries with
__proto__/constructor/prototypeas namespace poisonedObject.prototypeviansCoverage[e.ns]++, silently breaking MCP SDK request dispatch. Dict accumulators incomputeStatsnow useObject.create(null). (d773c4d) - Full audit.jsonl re-read on every
loadAuditLog()call — added incremental tail cache; subsequent calls read only new bytes. Cache resets on shrink/rotation. (1810fc2) - Full directory scan on every
scanAndSync()call — added dir-mtime fast-skip; if the store directory's mtime is unchanged, the cached entry state is authoritative and per-file scanning is skipped. (b803683)
- Object.prototype poisoning — telemetry entries with
- Numeric MCP tool params now coerce strings.
codex_audit limit,codex_stale days,codex_get depthusez.coerce.number()so MCP clients passing values as strings no longer hit validation errors. (4ef3800) - Validator-bypass + prototype-pollution: 7 latent bugs closed.
setValue,setAlias,codex_copy,codex_rename, andcodex_importhad inconsistent or absent key validation. See beta.0 entry for the full breakdown. - Interpolation
:?and circular detection now propagate errors instead of returning raw literals. codex_importpreview mode validates keys up-front.codex_import type=all(CLI) properly dispatches sections.codex_runno longer tagged as a redundant write.codex_statsnamespace coverage filters noise from failed ops, searches, and alias operations.handleError/printErrorshow underlying error message and setexitCode = 1.getNestedValueno longer walks the prototype chain —codex_get __proto__returns "not found" instead ofObject.prototype.codex_setwith invalid alias name now errors before creating the entry (no more partial-state on bad alias).
- Packaging: Beta binary installed as
ccli-betaviabrew install seabearDEV/ccli/ccli-beta(dash, not@beta). - Test count: 1167 → 1226 (+59 regression tests across validator, prototype-safety, perf, and session-consistency coverage).
Second packaging-only respin. No source-code changes; the only difference is in .github/workflows/release.yml where the beta channel now writes Formula/ccli-beta.rb (with class CcliBeta) instead of Formula/ccli@beta.rb (with class CcliAtBeta).
Why: Homebrew's Formulary.class_s only handles @<digit>-versioned formulas (like python@3.11, node@20). It does NOT handle @<letter> like @beta — trying to load ccli@beta.rb errors with Expected to find class Ccli@beta, which isn't even a valid Ruby identifier. Both beta.0 and beta.1 wrote the un-loadable file. The dash form sidesteps brew's class_s entirely: ccli-beta.rb → CcliBeta via the standard separator + capitalize transform.
The release workflow also now removes the obsolete Formula/ccli@beta.rb from the tap repo on the first beta tag after this fix lands, so existing tap users running brew update stop seeing the load error.
Install command changes: brew install seabearDEV/ccli/ccli-beta (with a dash, not @beta). Binary is still invoked as ccli-beta.
Packaging-only respin of v1.11.1-beta.0. No source-code changes; the only difference is in .github/workflows/release.yml where the Homebrew formula generator now installs the beta binary as ccli-beta instead of ccli. This lets ccli@beta coexist on a machine with stable ccli (no brew unlink cycling required to test the beta side-by-side). Stable formula generation is unchanged — still installs as ccli.
After upgrading from beta.0 to beta.1, the binary is invoked as ccli-beta (not ccli). If you already had beta.0 installed via brew, brew upgrade seabearDEV/ccli/ccli@beta will replace the keg and the new symlink will be ccli-beta.
First prerelease of v1.11.1, surfaced for beta-channel testing via brew install seabearDEV/ccli/ccli@beta before promotion to stable. The work is exclusively bug fixes and validator hardening — no new features. Found by an end-to-end MCP flogging session against the freshly-installed v1.11.0 binary; every fix has live verification + inline regression coverage.
These are bug fixes, but they change visible behavior in ways scripted callers might notice. None of the prior behaviors were documented as features — they were latent bugs surfacing as silent successes or empty responses — but flagging them anyway:
- Interpolation
${key:?required-message}now throws on a missing key. Pre-fix, the:?form silently returned the literal template (${key:?required-message}) instead of erroring. The success case (${present:?msg}→ returns the value) was always correct; only the failure case was broken. The codex docs always claimed:?was a "required check" — this aligns the behavior with the docs. - Interpolation circular references now throw with the full chain. Pre-fix, a cycle like
${a → b → a}halted at one expansion and returned the literal of the other side, with no surfaced error. Now throwsStrictInterpolationError: Circular interpolation detected: a → b → a. Subtree-fallback callers (the "raw template if interpolation fails" pattern ininterpolateObjectandcodex_get) explicitly re-throwStrictInterpolationErrorwhile still falling back for plain "key not found" errors. codex_get __proto__(andconstructor,hasOwnProperty,toString,valueOf,propertyIsEnumerable,isPrototypeOf,__defineGetter__, etc.) now return "not found". Pre-fix,codex_get __proto__rendered an empty subtree (becausegetValuereturnedObject.prototypeand the formatter walked its enumerable own properties — none);codex_get constructorreturned the source of theObjectconstructor function.getNestedValuenow usesObject.hasOwnper hop so the prototype chain is invisible to lookups.- Bad import keys now error instead of silently merging.
codex_import(MCP) andccli data import(CLI) used to report "merged successfully" for inputs like{"__proto__":"x"},{"constructor.prototype.polluted":"x"}, or{".dotleading":"x"}and persist nothing —expandFlatKeysandsetNestedValuesilently dropped the bad keys viaisSafeKey. Now both apply and preview paths run the newvalidateImport*family before any save, listing every invalid key in the error. codex_setwithalias=__proto__(or any invalid alias name) now errors before creating the entry. Pre-fix,setValuewould persist the entry, thensetAliaswould throw on the bad alias name — leaving the store in a partial state where the entry existed but the alias didn't, and the user only saw the error. The MCPcodex_sethandler now pre-validates the alias name before any writes.
- Validator-bypass + prototype-pollution: 7 latent bugs closed in one wave. End-to-end flogging found that
setValue,setAlias,codex_copy,codex_rename, andcodex_importhad inconsistent or absent key validation — each entry point had its own gate (or none), andsetNestedValue/expandFlatKeyssilently dropped names that hitisSafeKey's rejection list, producing phantom-write semantics where the response said "success" but nothing actually persisted on disk.- Bug 1:
resolveKeyleaked prototype-chain values.merged[cleanKey] ?? cleanKeyused unsafe property lookup; forcleanKey === "__proto__",merged.__proto__returnedObject.prototype(truthy), soresolveKeyreturned an object instead of a string. The object then propagated intosetValue → setNestedValuewhich crashed downstream withTypeError: path.split is not a function. Fix:Object.hasOwn(merged, cleanKey)for both the project-merged and scope-explicit lookups insrc/alias.ts. - Bug 2:
setValuehad no key validation. The only gate was at the file-system layer insideentryFilePath, which ran duringsave()— by thensetNestedValuehad already silently dropped bad keys viaisSafeKey, so the user saw "Set: foo = bar" with no actual persistence. Added anisValidEntryKeygate at the top ofsetValueinsrc/storage.ts.removeValuegot the same treatment (returnsfalseinstead of throwing for invalid keys, so callers probing with user input get "nothing removed" rather than a crash). - Bug 3:
isValidEntryKeyaccepted leading-dot, trailing-dot, and non-string keys..dotleadingslipped through;expandFlatKeysthen silently normalized it todotleading(becauseisSafeKey('')returned true for the empty first segment, the parent walk broke out, and the leaf got set on the result root). The store ended up with adotleading.jsonfile the read path could never find via.dotleading. Added explicitkey.startsWith('.') || key.endsWith('.')checks plus atypeof key !== 'string'defensive guard insrc/utils/directoryStore.ts. - Bug 4:
getNestedValuewalked the prototype chain.obj[keys[0]]returned inherited values for__proto__,constructor,hasOwnProperty, etc. Now usesObject.hasOwnper hop insrc/utils/objectPath.ts. This single fix closescodex_get __proto__,codex_copy dest=__proto__'s spurious "already exists" error, and several related symptoms. - Bug 5:
setAliasaccepted any string. Both alias name and target path are now validated viaisValidEntryKeyinsrc/alias.ts. Pre-fix,__proto__got silently dropped on persistence (JSON serialization quirk) and the empty string persisted as-> target, visible inalias_listas a phantom entry. - Bug 6:
codex_import(MCP and CLI) silently dropped bad keys and reported success. Three new validators insrc/storage.tswalk the import object viagetOwnPropertyNames+getOwnPropertyDescriptorto defeat the__proto__getter trap. Validation runs against the raw input (not the post-expandFlatKeysform) so leading-dot normalization can't erase the evidence. Wired into both MCPcodex_importand CLIimportDatafor entries, aliases, and confirm sections — includingtype=all. - Bug 7: Partial-state on
codex_setwith bad alias.setValueran first and persisted the entry, thensetAliasthrew on the invalid alias name. User saw an error but the entry was already saved. Now pre-validates the alias name in thecodex_setMCP handler before any writes.
- Bug 1:
- Interpolation
:?and circular detection now propagate errors instead of returning literals. Two related bugs in the same code path:interpolateObjectand thecodex_getsingle-key handler both wrappedinterpolate()intry/catch { return raw }, swallowing every interpolation error including the load-bearing:?required check and the circular-reference detection. NewStrictInterpolationErrorclass insrc/utils/interpolate.tsis thrown from those paths; subtree fallback re-throws it instead of catching, while still allowing plain "key not found" errors to fall back to raw so a single broken leaf doesn't fail an entire subtree get. codex_importpreview mode now validates keys. Pre-fix, the preview branch ran throughflattenObject(expandFlatKeys(input))which trips the__proto__getter trap and silently drops bad keys from the diff. Users saw a clean preview that omitted the bad keys, then got an error on apply. Both MCP and CLI preview branches now run the newvalidateImport*helpers up-front, so the preview either matches what apply would do or fails with the same error.codex_import type=all(CLI) now properly dispatches sections. Pre-fix, the CLIimportDataran three top-level branches (if entries || all,if aliases || all,if confirm || all) all against the samevalidData, so an--allimport file shaped{entries:..., aliases:..., confirm:...}got saved as entries with literal top-level keys"entries"/"aliases"/"confirm"AND tried to save the whole wrapper as aliases (which then failed thehasNonStringValuescheck). The MCPcodex_importhandler always split sections correctly; this brings the CLI into line. An--allimport with no recognized sections now errors cleanly instead of silently saving the wrong shape.codex_runno longer tagged as a redundant write. The MCP wrapper'sredundantflag was checked againstisWrite(which includesop === 'exec'), so codex_run was always tagged redundant — the stored command never changes during a run, so before === after is trivially true. Now requiresop === 'write', which excludes exec ops. Audit log entries for runs are clean (noredundanttag, no spurious "value didn't change" diff lines).codex_statsnamespace coverage hides noise. Three sources of phantom namespaces in the dashboard:- Failed operations: rejected validator writes (
_aliases,flog/,__proto__, etc.) showed up as namespaces with 1 write each. The wrapper now plumbssuccessthrough to telemetry andcomputeStatsfilters onsuccess === false. codex_searchkeys: search terms (regex patterns sliced on.) produced phantom namespaces like^arch\andflog/. Filter ontool === 'codex_search'.codex_alias_set/codex_alias_removekeys: alias names likechkorflog_test_aliaswere treated as entry namespaces. Filter on those tool names too.
- Failed operations: rejected validator writes (
handleErrorandprintErrornow show the underlying error message AND setprocess.exitCode = 1. Three CLI bugs surfaced by smoke-testing the beta build itself before tagging:handleErrorswallowed the underlying error in non-DEBUG mode — printed onlymessage(e.g. "Failed to set entry:") and threw away the error text. CLI users hit this when invalid keys produced "Failed to set entry:" with no detail; the actual reason ("Invalid store key: proto") was only visible withDEBUG=true. Both branches now show<message>: <error>, with the stack trace gated on DEBUG.handleError+printErrorreturned 0 on most error paths — scripts wrappingcclicouldn't distinguish success from failure. Both helpers now setexitCode = 1; every existing call site was already followed by areturn/abort, so this matches intent without behavioral surprise. Affects every CLI failure path that goes through either helper (set/get/run/import/export/etc.).showImportPreview(CLIdata import --preview) used the same broken top-level dispatch as the old apply path — three branches all running against the wrappervalidData, so an--allimport file shaped{entries:..., aliases:..., confirm:...}got diffed as if the wrapper itself were the entries (showing[add] entries.foo: bar,[add] aliases.alias: target, etc.). The apply path was fixed earlier in this release; this brings preview into line so what you see in the preview matches what apply would do.
- Test count: 1167 → 1225 (+58 regression tests). Distributed across
storage.test.ts(validator gate + import validators + handleError format),alias.test.ts(setAlias validation),objectPath.test.ts(getNestedValue prototype safety),interpolate.test.ts(strict error propagation),telemetry.test.ts(namespace filter),session-consistency.test.ts(MCP-source case), andcommands.test.ts(CLI type=all dispatch).
codex_import previewnow fails up-front for invalid keys; if you have automation that expected the prior "silent merge with empty diff" behavior, that automation needs to handle the new error response.- The session ID unification fix (PR #67 in v1.11.0) was correct in source but was masked in production by stale long-running MCP server processes that predated the fix. This release adds an MCP-source case to
session-consistency.test.tsso the gap is no longer in test coverage. If you saw mismatched session IDs in youraudit.jsonlvstelemetry.jsonlafter upgrading to v1.11.0, restart your MCP server. codex_get __proto__returning "not found" is the correct behavior, but if anything in your tooling was scraping the prior empty-subtree response, it'll now see an error response.- The
flog.*namespace anddotleadingorphans seen incodex_statsfrom v1.11.0 testing are historical telemetry — they're stuck in~/.codexcli/telemetry.jsonluntil you reset the log. New entries from this beta will not pollute the dashboard.
Three breaking items in v1.11. Each is small individually; review before upgrading if you script against the CLI.
withFileLockfails closed in production. Lock acquisition failures now throw instead of silently running the closure unlocked. All three production call sites (directoryStore.save(),migrateFileToDirectory(),saveJsonSorted()) were audited and confirmed to have a guaranteed-existing parent directory before invokingwithFileLock, so the new throw never fires in normal operation. SetCODEX_DISABLE_LOCKING=1to restore the pre-v1.11 silent fallback (test-only escape hatch).--raw/-rongetandcontextremoved entirely. Was a deprecated alias for--plainsince v1.9.1. Migration: replace any scripted use ofccli get foo --raworccli context --rawwith--plain(or the new-pshort). The deprecation warning has been live for two minor versions.stats --detailedandaudit --detailedmoved from-dto-D. Frees lowercase-dto mean--decryptunambiguously across all read commands (get,run,edit). Mirrors the-G/-A/-Pcapital-letter convention for "broadeners". Anyone passing-dtoccli statsorccli auditwill get a usage error.
-pshort for--plainongetandcontext(closes the original short-flag-audit trigger fromcontext.shortFlagAudit). Closes #62.-jshort for--jsononstats— every other JSON-emitting command already had-j;statswas the lone exception. Pure consistency fix.CODEX_DATA_DIRvalidation, provenance, and documentation — the env var has always been honored bygetDataDirectory(), but was undocumented, unvalidated, and invisible. Closes #63.- Validation:
CODEX_DATA_DIRmust be an absolute path. Relative values (./mydata, etc.) now throw with a clear error rather than silently resolving againstprocess.cwd(). Empty strings are treated as unset. - Provenance:
ccli infoannotates theDataline with(CODEX_DATA_DIR)when the env var is set, so users can verify their override at a glance. - Writability warning: if the resolved data directory exists but isn't writable, a one-time warning fires to stderr on first
getDataDirectory()call. - Docs: new
## Environment Variablessection in the README listing everyCODEX_*variable with purpose, default, and notes.
- Validation:
clearDataDirectoryCache()insrc/utils/paths.ts— resets the module-level cache and one-shot flags so a subsequentgetDataDirectory()call re-readsCODEX_DATA_DIR. MirrorsclearProjectFileCache(). Primarily for tests.isDataDirectoryFromEnv()insrc/utils/paths.ts— small predicate soccli info(and tests) can label the data path with its source.CODEX_DISABLE_LOCKING=1env var — test-only opt-out that restores the pre-v1.11 silent-fallback behavior ofwithFileLock. Documented in the README env-var section._README.mdhand-edit warning sidecar — file-per-entry layout's big UX win is that per-entry files are browsable in a file manager, but that also invites developers to tweak them directly (which desyncs per-entry metadata and breaks staleness signals — seeconventions.editSurface). The store now seeds a_README.mdon firstsave()and during migration with an in-context nudge pointing at the supported edit paths. Idempotent: a user-customized_README.mdis never overwritten.- Release checklist at
docs/release-checklist.md— captures the manual smoke steps for the v1.11 breaking changes plus a reusable per-release template.
- Short-flag namespace audit — first comprehensive pass at the short-flag space since v1.0. Three flag moves, one orphan adoption, one consistency fix. See the Breaking Changes section for the
-d→-Dmove onstats/audit. The other two changes are strictly additive (-pfor--plain,-jforstats --json). Closes #62. GetOptions.rawfield renamed toplaininsrc/types.tsandContextOptions.raw→plaininsrc/commands/context.ts. Internal API change; downstream consumers insrc/commands/entries.ts,src/commands/context.ts, andsrc/formatting.ts(displayTree/formatTree) updated to match.loadConfig()returns defensive shallow copies of the cachedConfig— same hazard PR #58 fixed for sidecar caches indirectoryStore.ts, found during the defensive shallow-cache audit.setConfigSetting()callsloadConfig(), mutates the result in place, then callssaveConfig()with the mutated reference; under the previous shared-reference behavior, the in-memory cache would be polluted by those mutations between the write and the next mtime-triggered re-read. All three return paths (cached, freshly-parsed, ENOENT/error fallback) now return copies;saveConfig()also stores a copy in the cache for defense in depth.- File-per-entry store: sidecar mtime tracking —
_aliases.jsonand_confirm.jsonwere re-read and JSON-parsed on everyscanAndSync(), even when nothing had changed. They now go through the same mtime-cached path as entry files: a stat-first refresh skips the re-read when mtime matches. Missing sidecars cache as an-1sentinel so they're detected the moment they appear on disk.load()now returns defensive shallow copies of the cached sidecar maps so callers that mutate-then-save (setAliasand friends) can't accidentally pollute the cache. - Legacy
-aflags onget/rename/removeare now hidden from--help— these are undocumented entry points to the alias-subcommand functionality (ccli get -a≡ccli alias list, etc.). They still work for back-compat but no longer appear in--helpoutput. The canonical paths are thealiassubcommands. (set -aandfind -aare documented and remain visible — seearch.clicodex entry for the rationale.)
--raw/-rongetandcontext— see Breaking Changes #2. Closes #62.
- Telemetry consistency: shared session ID between audit and telemetry, accurate CLI
responseSizemeasurement — three related logging bugs that broke cross-log analysis and undercounted CLI traffic incodex_stats. Found by inspection of live~/.codexcli/audit.jsonlandtelemetry.jsonl.- Bug 1 — independent session IDs:
src/utils/audit.tsandsrc/utils/telemetry.tseach generated their own randomsessionIdat module-load time. Same operation written to both files would have differentsessionvalues, breaking any analysis that joined the two logs by session. Fix: extracted a single sharedsessionIdsource intosrc/utils/session.ts; both audit and telemetry now importgetSessionId()from there.telemetry.tsre-exports the helper for backward-compat with the existingMissPathTrackerconsumer. - Bug 2 — CLI reads silently logged
responseSize: undefined: the CLI wrapper atsrc/utils/instrumentation.ts:142computedresponseSizefrom theaftervalue, which is only set for writes. Every CLI read recordedresponseSize: undefined, so thecodex_stats"data served" / delivery-cost metric only counted MCP traffic. Token-savings calculations were overstated for CLI-heavy users because the delivery cost (subtracted from gross savings) was undercounted. Fix: newsrc/utils/responseMeasure.tsstate machine. The CLI wrapper monkey-patchesprocess.stdout.writeto count bytes viaaddResponseBytes()while a measurement is active;withPagercallsaddResponseBytes()directly when flushing to a spawned pager (the only path that bypasses the wrapper'sstdout.writehook). - Inconsistency 3 —
responseSizesemantic mismatch between CLI and MCP writes: MCP wrote the actual response-text size; CLI wrote the after-value size. Different concepts behind the same field name. Fixed automatically by Bug 2's fix — both now measure "bytes the user actually received" (stdout output for CLI, response payload for MCP). - Tests: 13 new test cases. New
responseMeasure.test.ts(7 tests, state machine basics + edge cases). Newsession-consistency.test.ts(3 tests, regression coverage for Bug 1 —logAuditandlogToolCallproduce matchingsessionfields). 3 new integration tests inentries-advanced.test.tsexercising the wrapper end-to-end viaexecSync(CLI read records non-zeroresponseSize, CLI write recordsresponseSizematching the printed confirmation, audit + telemetry sessions match for the same op).
- Bug 1 — independent session IDs:
- File-per-entry store: torn reads during concurrent writes —
load()could observe a partially-committedsave()when another process was mid-write (some entries updated, others not), with no way to detect it. The store now uses a seqlock-style commit epoch in a new_epoch.jsonsidecar: even values mean "stable," odd means "writer mid-commit."save()bumps the epoch to odd before touching any files and to the next even value after all writes complete, both under the existing directory lock.load()snapshots the epoch before and after its scan and retries (bounded to 3 attempts with 1–4 ms backoff) if it sees a mismatch or an odd "before" value. Missing or bogus_epoch.jsonreads as 0, so legacy directories and fresh installs transition cleanly through the first save. - File-per-entry store: migration race on pristine installs —
migrateFileToDirectoryran without a lock. Two processes starting simultaneously on a pristine install could both enter the migration path and race. The migration now runs insidewithFileLock(newDirPath, …), reusing the same lock key as the steady-state store, so migrations and normal saves are mutually exclusive. The loser waits, observes the new directory, and returnsalready-present. Migration also seeds_epoch.jsonat 0 inside its tmp directory before the atomic rename, so readers see a coherent epoch from the instant the store directory exists. withFileLockfails closed in production — see Breaking Changes #1. Closes #61.
- File-per-entry store layout —
.codexcli.json(project) and~/.codexcli/data.json(global) are replaced by a.codexcli/directory (project) and~/.codexcli/store/directory (global). Each entry lives in its own file as<dotted-key>.jsonwith a{value, meta: {created, updated}}wrapper. Store-level state lives in sidecar files_aliases.jsonand_confirm.json. Automatic, idempotent migration runs on first access after upgrade; old files are renamed to.backup. No user-visible CLI or MCP changes — the in-memory shape returned by every store-layer function is identical. Closes #54.- Why: the old single-file layout produced merge conflicts for multi-dev projects whenever two developers added different entries on parallel branches — both writes touched the same JSON region and git textual merge fought them. Per-entry files eliminate that entire class of conflicts: git merges the directory file-by-file, so different-key concurrent edits no longer conflict at all, and same-key edits (the rare case where you actually want a human looking) remain visible in the diff.
meta.createdfrom day one — every entry wrapper gets bothmeta.created(set on first write, preserved across updates) andmeta.updated(bumped on every write). Migrated entries preserve the legacy_meta[key]timestamp as both fields; entries that had no legacy timestamp migrate as[untracked](nometablock) soccli stalecontinues to surface them accurately.- Hand-editing is unsupported — the wrapper format assumes only the CLI, MCP tools, or a future UI touch the files. Direct edits desync per-entry metadata (staleness, future provenance fields) and break the wrapper contract. Documented as
conventions.editSurfacein the codex. - Dirty-tracking save() — only files whose wrapper changed are rewritten, so single-entry updates touch exactly one file instead of rewriting all N.
- Bulk-op atomicity —
reset --entriesandimport --replacebuild the new state in a sibling.codexcli.tmp/directory and swap atomically via double-rename; failure mid-swap leaves the old state intact and is self-cleaned on next startup. autoBackupnow recursively copies the new store directory viafs.cpSync, plus any lingering legacy files as fallback.ccli initcreates a.codexcli/directory (not a file) and seeds empty_aliases.json/_confirm.jsonsidecars.ccli init --removeandccli project --removeusefs.rmSyncwhich handles both the new directory and legacy file uniformly.
findProjectStoreDir()insrc/utils/paths.ts— purpose-built resolver that walks up looking for a.codexcli/directory specifically, used by store internals.findProjectFile()remains as the general-purpose "does a project exist, where is it?" query and now recognizes both the new directory and the legacy file, preferring the directory when both exist.getGlobalStoreDirPath()insrc/utils/paths.ts— returns~/.codexcli/store/, the v1.10.0 global store location.- Design decision entries in the codex —
arch.storeLayoutcaptures the decision and rationale;conventions.editSurfacecodifies the "CLI / MCP / future UI only" rule. Future sessions inherit both without relitigating.
createScopedStorefactory insrc/store.ts— replaced entirely bycreateDirectoryStoreinsrc/utils/directoryStore.ts. Public API (loadEntries,saveEntries,loadMeta, etc.) is unchanged; only the private implementation behind it.ScopedStore.prime()— removed from the interface and implementation. It was a no-op carried forward from the legacy migration cache; the new migration path writes the directory directly and does not need it.
- MCP scope fallback was silent — when no
.codexcli.jsoncould be resolved (client doesn't advertiserootsandCODEX_PROJECTisn't pinned),codex_setwith no explicitscopewould silently fall through to the global store, so project-specific writes landed in the user's global store with no indication.codex_contextnow leads with[project: <path>]or a[project: NONE — ...]banner so agents know up-front where writes will land.codex_setnow appendsWrote to: project|globalon every write, plus a remediation hint (pin CODEX_PROJECT or pass scope:"project" explicitly) when an unscoped write fell through to global. Both changes are additive — no schema changes.
- Interpolation backslash escape —
\${key}and\$(key)now emit literal${key}/$(key)with the backslash consumed. Prevents stored documentation or examples containing interpolation syntax from triggering resolution errors on read. --plainflag ongetandcontext— replaces the misleadingly-named--raw, which implied "no processing" when its actual behavior was "no colors".-r/--rawis kept as a hidden, deprecated alias and prints a one-line deprecation warning. Closes #40.CODEX_PROJECTenv var — explicit override for the project file location. Accepts a path to a.codexcli.jsonfile or its containing directory. Fails closed if the path doesn't exist (no silent walk-up to a different project), so it's safe to pin in.claude.jsonMCP blocks.- MCP client roots support — the MCP server now calls
roots/listafter the initialize handshake and uses the first advertised root as the project file search start. Best-effort and silent for clients that don't implement roots.
- MCP server bound to the wrong project —
findProjectFile()walked up fromprocess.cwd(), which silently bound the server to whichever.codexcli.jsonlived above its inherited cwd. The new resolution order is:CODEX_NO_PROJECT→CODEX_PROJECT→setProjectRootOverride()(set from MCP roots and from launcher hints) →process.cwd()walk-up. The pre-existingCODEX_PROJECT_DIRand--cwdlauncher hints still work but now apply via the override (noprocess.chdir) and work whether the server is run as a binary or imported. arch.interpolationcodex entry — was self-poisoned by its own${key}examples, causing"key" not founderrors on read. Rewritten to use prose descriptions. Also corrected the claim that--rawskips interpolation (it's--source).
- Cleared pre-existing lint backlog — fixed 6 ESLint errors that had accumulated since v1.9.0, so
npm run lint(and thecommands.checkalias) is green again. No behavior changes:prefer-nullish-coalescing,prefer-regexp-exec,no-floating-promises(all targets aresync=trueand resolve immediately, markedvoid), andno-unnecessary-type-assertion.
- Net token savings —
ccli statsandcodex_statsnow report delivery cost (tokens consumed by cache hits) and net savings (gross exploration avoided minus delivery cost). Encourages lean, high-signal knowledge bases. - Miss-path tracking — MCP server tracks exploration cost when
codex_get/codex_searchmisses. Opens a "miss window" that records subsequent tool calls until the agent finds the answer (writeback), moves on, or times out. Stored in~/.codexcli/miss-paths.jsonl. - Self-calibrating exploration costs — static per-namespace cost multipliers are replaced with observed medians once 5+ writeback miss-path samples exist.
--detailedstats show[observed, n=N]vs[static]per namespace. Calibration status summary in detailed output. MissWindowTrackerclass — pure state machine insrc/utils/telemetry.tswith no I/O, fully testable. Handles window lifecycle: open on miss, accumulate on subsequent calls, close on writeback/moved_on/timeout.miss-pathsreset type —ccli reset miss-pathsandcodex_reset type:"miss-paths"to clear the miss-path log.- 30 new tests —
miss-path.test.ts(MissWindowTracker lifecycle, persistence roundtrip, calibration thresholds), extendedtelemetry-advanced.test.ts(net savings, calibration, backward compat).
- MCP telemetry missing
projectfield —logToolCall()now self-resolves the project directory viafindProjectFile(), matchinglogAudit()'s behavior. Previously relied on the caller to pass it, which was inconsistent. - Fuzz test timeout — encrypt/decrypt round-trip test (50 trials) now has a 15s timeout instead of the default 5s.
- MCP test mocks —
mcp-server.test.tsandmcp-advanced.test.tsmocks updated for new telemetry exports (MissWindowTracker,appendMissPath,getSessionId,extractNamespace).
- Stats display updated — "Est. tokens saved" line now shows "exploration avoided" instead of "agent tool calls avoided". Delivery cost and net savings lines added below. Per-namespace breakdown includes calibration tags.
- Token savings documentation —
docs/token-savings.mdrewritten with miss-path calibration methodology, net savings explanation, updated diagrams and worked example. - LLM instructions —
codex_statsdescription updated to mention net savings, delivery cost, and calibration.
aliassubcommand group —alias set <name> <path>,alias remove <name>,alias list,alias rename <old> <new>. Dedicated alias management replacing scattered-aflags.confirmsubcommand group —confirm set <key>,confirm remove <key>,confirm list. Dedicated confirmation management replacingset --confirm/--no-confirm.contextcommand — CLI equivalent of MCPcodex_contextwith--tierfiltering (essential, standard, full),--json,--raw.infotop-level command — promoted fromconfig info. Shows version, entry counts, storage paths.searchhidden alias —ccli searchworks as an alias forccli find, matching MCPcodex_searchnaming.- Enhanced
ccli init— codebase scanner with 6 composable detectors (project, commands, files, deps, conventions, context) and ~50-entry known-deps lookup table. GeneratesCLAUDE.mdwith AI agent behavioral directives. Seedsconventions.persistence(three-file balance rule) andcontext.initialized(agent-driven analysis marker). Flags:--no-scan,--no-claude,--force,--dry-run. - Agent-driven first-session analysis — LLM instructions and CLAUDE.md template include FIRST SESSION guidance. Agents detect fresh scaffold via
context.initializedmarker and automatically perform deep codebase analysis (populatearch.*,context.*, enrichedfiles.*). - Centralized CLI instrumentation —
withCliInstrumentation()wrapper insrc/utils/instrumentation.ts. All 22 CLI commands now have full telemetry + audit logging with parity to the MCP server wrapper. - Shared instrumentation helpers —
SKIP_AUDIT,BULK_OPS,captureValueextracted from MCP server and shared between CLI and MCP wrappers. - Knowledge Flywheel section in README — explains how the knowledge base compounds across sessions and agents.
- 68 new tests —
scan.test.ts(44),claude-md.test.ts(11),init.test.ts(13),context.test.ts(6),cli-restructure.test.ts(19).
- CLI audit parity — previously untracked commands now fully instrumented:
run,edit,alias list,alias rename,confirm set/remove/list,context,lint,config set/get,export,import,reset,init. scaffoldProject()refactored — inline manifest parsing replaced withscanCodebase()fromsrc/commands/scan.ts.filterEntriesByTierextracted — moved frommcp-server.tstosrc/commands/context.ts, shared between MCP and CLI.- Help text updated — new commands, subcommands, updated
finddescription, completions table. initdescription updated — from "Create project-scoped .codexcli.json" to "Initialize project (.codexcli.json + CLAUDE.md)".
get -a— usealias listinstead (prints notice, still works)remove -a— usealias removeinsteadrename -a— usealias renameinsteadinit --scaffold— scanning is now the default (use--no-scanto skip)data projectfile— useinitinstead
- Staleness awareness in context/get —
codex_contextandcodex_getappend[untracked]/[Nd]age tags to stale entries. CLIgetprints yellow warning for stale entries. - Exploration-weighted token savings —
codex_statsestimates tokens saved per namespace using weighted exploration cost multipliers. Bootstrap estimation based on response size and entry count. Per-namespace breakdown in--detailedoutput. EXPLORATION_COSTmap — exported from telemetry.ts for transparency. Documents estimated exploration cost per namespace (files: 2000, arch: 3000, commands: 1000, etc.).- Comprehensive test suite expansion — 633 → 1048 tests across 46 files. Includes concurrency stress tests, MCP integration with real I/O, property-based fuzz tests, store/storage layer tests, telemetry boundary cases.
- CLI audit enrichment — CLI entries now include
duration,responseSize,hit/miss,redundant, andentryCountmetrics.cclid audit --detailedshows per-entry metrics for both CLI and MCP entries. - CLI read audit entries —
get,find/search, andstalecommands now create audit entries with hit/miss tracking and entry counts. - Token savings estimate —
codex_statsandcclid statsnow show estimated tokens saved via cache hits and bootstrap context reuse (~4 bytes/token). - Per-agent breakdown —
CODEX_AGENT_NAMEis tracked in telemetry.--detailedstats show per-agent call/read/write counts. - Sync CLI logging —
logAuditandlogToolCallacceptsyncflag for reliable CLI writes that survive process exit. - 11 new computeStats tests — hit rate, redundant rate, session duration, response bytes, trends, token savings, agent breakdown, edge cases.
- 2 new sync write tests — verify
appendFileSyncpath for CLI audit and telemetry. searchEntriesreturns match counts — enables hit/miss and entryCount tracking for search audit entries.
- CLI audit/telemetry lost on process exit — CLI used async
appendFilebut the process exited before callbacks fired. Now usesappendFileSyncfor all CLI calls. - Batch
set --globalwrote to wrong scope — batch mode did not forwardoptions.globaltosetEntry. Entries went to project scope instead of global. - Redundant writes marked as failures —
successcheck requiredbefore !== after, so same-value writes appeared as failures. Now usesexitCode-based success with separateredundantflag. - Batch set missing
redundantflag — only single-key set tracked redundancy. Batch path now detects and flags redundant writes.
- Two-step MCP confirmation —
codex_runfor--confirmentries returns a one-timeconfirm_token(5min TTL) on first call. Pass token back to execute.force:trueanddry:truebypass. - Redundant write detection — MCP audit entries now flag writes where before/after values are identical.
- Enriched audit/telemetry metrics —
duration,responseSize,requestSize,hit/miss,tier,entryCount,redundantfields in MCP audit entries. --detailedflag —codex_auditandcclid auditshow per-entry metrics when--detailedis passed.- Token-efficiency section in stats — hit rate, redundant write rate, response bytes, avg latency.
--hits,--misses,--redundantaudit filters — query audit log by cache effectiveness.
- Telemetry race condition — concurrent MCP calls could interleave JSONL writes. Added pending-write tracking.
- Regex injection in search — code scanning alert resolved for user-supplied regex patterns.
- SECURITY.md — added vulnerability reporting policy.
- Schema guide — documented recommended namespaces and prefer-MCP guidance.
- Agent-agnostic optimizations — enriched MCP tool descriptions, tier guidance, deduped arch/files entries.
- Test isolation —
CODEX_DATA_DIRredirects audit/telemetry to temp dir during tests. conventions.persistence— clear lanes for.codexcli.json,CLAUDE.md,MEMORY.md.
- Tiered
codex_context—essential,standard(default, excludesarch.*),fulltiers to control context size. files.*namespace — key file paths and their roles stored in project data.- CLAUDE.md overhaul — bootstrap instructions, prefer-MCP guidance, write-back reminders.
- Data cleanup — removed duplicate arch/files entries, enriched tool descriptions.
- Audit UI redesign —
cclid auditwith before/after diffs, collapsed dates, color-coded status. - Source filters —
--mcpand--cliflags to filter audit entries by source. - Log reset support —
cclid data reset logsto clear audit and telemetry logs.
- DRY cleanup — extracted
parsePeriodDays, shared log paths, unified audit filtering.
- 75 lint errors resolved — auto-fixed redundant type constituents, switched to nullish coalescing where safe, added
voidto fire-and-forget telemetry/audit promises, suppressed unavoidableanyin dynamic MCP tool wrapper. - Prototype pollution in
deepMerge()— addedisSafeKey()guard to block__proto__,constructor, andprototypekeys during JSON import merges. - Audit/telemetry log file permissions — explicit
0o600mode onappendFileso logs are created owner-readable only. - Predictable temp file names in edit — replaced
Date.now()naming withfs.mkdtempSync()for secure temp directory creation. - Encrypted values in audit params —
sanitizeParams()now masks encrypted values as[encrypted]in addition to redacting passwords. - Test data removed from
.codexcli.json— cleaned leakedtest.*andsearch.test.*entries from project data file.
- Audit log — full mutation tracking at
~/.codexcli/audit.jsonl. Captures before/after values, success/fail, scope, agent identity, and sanitized params for every write operation. Encrypted values masked, passwords redacted. codex_auditMCP tool — query the audit log with key filter, time period, writes-only, and limit.ccli audit [key]CLI command — browse audit entries with diff-style before/after display. Supports--period,--writes,--json,--limit.- Scope tracking in telemetry — telemetry now tracks scope as
project,global, orunscopedfor unresolved/auto cases. Stats display shows scope breakdown. --agentflag onccli mcp-server— setsCODEX_AGENT_NAMEfor audit attribution. Also readable via env var.
codex_alias_removescope bug (#36) — MCP handler now usesremoveAlias()which correctly falls through project → global, instead of manual merged-map delete that silently succeeded on the wrong scope.codex_staleandcodex_lintclassification — now correctly classified as read ops instead of meta.
- Unified CLI + MCP telemetry — CLI commands now log to telemetry alongside MCP calls. Stats display separates MCP sessions from CLI calls.
.codexcli.jsonoverhauled — tightened entries, removed redundantfiles.*namespace, addedproject.vision,project.install,context.devWorkflow, full_metatimestamps.
codex_contextMCP tool — returns a compact flat summary of all stored project knowledge in one call. Designed for AI agents to bootstrap context at session start.CODEX_PROJECT_DIRenvironment variable — alternative to--cwdfor telling the MCP server where the project root is.- Recommended schema — documented namespace conventions (
project.*,commands.*,arch.*,conventions.*,context.*,files.*,deps.*) for organizing project knowledge. - AI agent workflow — LLM instructions rewritten to guide agents on bootstrapping from stored context, recording discoveries, and maintaining the knowledge base.
- CodexCLI's own
.codexcli.jsonpopulated with real project data as a living example.
ccli init— top-level command to create/remove project-scoped.codexcli.json(replacesccli data projectfile).--all/-Aflag onget— shows entries from both project and global scopes with section headers.- MCP
codex_get:allparameter for listing both scopes.
ccli getnow shows project entries only when inside a project directory. Previously showed merged project + global entries with[P]markers. Use-Gfor global only,-Afor both.- Single-key lookups (
ccli get specific.key) still fall through project → global transparently. ccli data projectfileis now a hidden alias forccli init.- Removed
[P]prefix markers from listing output.
mcp-server --cwd <dir>— set the working directory for the MCP server so it detects project-scoped.codexcli.jsonfiles. Pass this when registering the server (e.g.,claude mcp add codexcli -- ccli mcp-server --cwd /path/to/project).- Updated default LLM instructions to guide AI agents on using project vs. global scope.
- Project-scoped data —
ccli data projectfilecreates a.codexcli.jsonin the current directory. Project entries take precedence on reads, with automatic fallthrough to global data. Useccli data projectfile --removeto delete. --global/-Gflag onset,get,run,find,copy,edit,rename,remove— explicitly target the global data store when a project file exists.--global/-Gand--project/-Pflags ondata export,data import,data reset— scope data management operations to a specific store.- MCP
scopeparameter — all data-touching MCP tools (codex_set,codex_get,codex_remove,codex_copy,codex_search,codex_run,codex_alias_*,codex_export,codex_import,codex_reset) accept optionalscope: "project" | "global". - Tab completion for
data projectfilesubcommand and--global/-Gflags on all data commands. config infonow shows project file path (or "none") alongside the unified data file path.
- Unified data file — entries, aliases, and confirm metadata are now stored in a single
data.json(format:{ entries, aliases, confirm }). Existing separate files (entries.json,aliases.json,confirm.json) are auto-migrated on first access and backed up as.backup. config infonow shows a single "Data" path instead of separate Entries/Aliases/Confirm paths.
- MCP server LLM instructions — the MCP server now sends instructions to connected AI agents on initialization, guiding default behavior (e.g., prefer reads over writes). Built-in defaults work out of the box; users can override by setting
system.llm.instructions.
--depth/-k <n>flag onget— limit key depth for progressive browsing (e.g.,-k 1for top-level namespaces,-k 2for two levels). Works in both flat and tree modes.- MCP
codex_gettool: addeddepthparameter for depth-limited key listing
getdefault output is now keys-only —ccli getnow lists keys without values, reducing noise as the data store grows. Use-v/--valuesto include values. Leaf values (e.g.,ccli get server.ip) always show their value.- MCP
codex_gettool: addedvaluesparameter (defaultfalse; leaf values always include their value)
- Prototype-polluting function in nested object helpers (code scanning alerts #1 and #2)
- Bump hono from 4.12.0 to 4.12.7
- Bump @hono/node-server from 1.19.9 to 1.19.10
- Bump express-rate-limit from 8.2.1 to 8.3.0
- Bump flatted from 3.3.3 to 3.4.2
- Bump minimatch from 10.2.2 to 10.2.4
- Bump rollup from 4.57.1 to 4.59.0
- Exec interpolation
$(key)— reference a stored command with$(key)and its stdout is substituted at read time. Works inget,run, and tree display. Results are cached per interpolation pass so the same command only executes once.- Supports recursion: stored commands can themselves contain
${key}or$(key)references - Circular reference detection across
${}and$()boundaries - 10-second timeout per command execution
--source/-sshows the raw$(key)syntax without executing
- Supports recursion: stored commands can themselves contain
- Tab completion for
:composition inrun/r— e.g.ccli r cd:paths.<TAB>completes the segment after: - Namespace prefixes in
get/gtab completion —ccli g paths<TAB>now includespathsas a candidate so zsh stops at the namespace boundary instead of forcingpaths.
- Zsh completion script: colons in completion values (from
:composition) no longer break_describeparsing - Bash completion script: colons no longer cause word splitting issues (removed
:fromCOMP_WORDBREAKS)
copycommand (aliascp) — copy an entry or subtree to a new key, with--forceto skip confirmation--capture/-cflag onrun— capture stdout for piping instead of inheriting stdio--preview/-pflag ondata import— show a diff of add/modify/remove changes without modifying data- Batch set with
key=valpairs — e.g.ccli set a=1 b=2 c=3 - MCP
codex_copytool — copy entries via MCP with optionalforceto overwrite - MCP
codex_import:previewparameter to return diff text without importing - MCP
codex_run:captureparameter for API consistency (MCP already captures output) --version/-Vnow shown in main help under global options
- Main help (
ccli --help) now shows only commands, subcommands, and global options; per-command options moved to<command> --helpsubmenus setcommand description updated to reflect batch mode support
- Nested subcommand
--helprouting — e.g.ccli data import --helpnow correctly shows import options instead of falling through to root help editwas missing from the tab-completion commands list
editcommand (aliase) — open an entry's value in$EDITOR/$VISUALwith--decryptsupport--json/-jflag ongetandfindfor machine-readable JSON output- Stdin piping for
set— read value from stdin when piped (echo "val" | ccli set key) confirmas a standalone type fordata export,data import, anddata reset- Advisory file locking (
fileLock.ts) — all writes are lock-protected with stale-lock detection - Auto-backup before destructive operations (
data reset, non-mergedata import) in~/.codexcli/.backups/ - MCP
codex_set:encryptandpasswordparameters for encrypted storage - MCP
codex_get:decryptandpasswordparameters for encrypted retrieval - MCP
codex_run:forceparameter to skip confirm check on protected entries - MCP
codex_export,codex_import,codex_reset: support forconfirmdata type - Windows clipboard support via
clipcommand dev:watchnpm script — runstsc --watchfor automatic recompilation during developmentlintnpm script with ESLint andtypescript-eslint(type-checked + stylistic rulesets)
startnpm script — redundant withccliddevnpm script — broken with path aliases and redundant withcclidprepublishnpm script — not used (SEA distribution)
showExamples()referenced non-existent flags-k,-v,-e— now uses valid flagsshowHelp()config signature and subcommands were incorrect — now shows<subcommand>with correct listdisplayAliasesempty-state message referenced deleted command — now showsset <key> <value> -a <alias>data export all -o <file>overwrote the same file three times — filenames now suffixed with type- MCP
codex_runignoredconfirmmetadata — now checks confirm before executing - Data files used default permissions (0644) — now use 0600; directories use 0700
- Hierarchical data storage with dot notation paths
- Command runner with confirmation prompts and dry-run support
- Rich output formatting with color-coded output and tree visualization
- Alias system for frequently accessed paths
- Search with filtering by entries and aliases
- Configuration system (colors, themes)
- Data import/export (JSON format)
- Shell tab-completion for Bash and Zsh
- MCP server for AI agent integration (Claude Code, Claude Desktop)
- Interpolation with
${key}syntax - Value encryption with password protection
- Shell wrapper for running builtins in the current shell
- Clipboard integration
- Per-entry run confirmation (
--confirm/--no-confirmflags,confirm.json) renamecommand for entry keys and aliases (--set-aliasflag)--forceflag onremoveto skip confirmation prompt--sourceflag forgetandrun(show stored value before interpolation)cachedStoreutility with mtime-based caching for aliases, confirm, and data stores- First-run prompt to install shell completions and wrapper
- Consolidated CLI from 13 top-level commands to 7 (
set,get,run,find,remove,config,data) - Moved
export,import,resetunderdatasubcommand - Moved
info,examples,completionsunderconfigsubcommand runcommand now accepts variadic keys with&&chaining and:composition- Removed
--prefixand--suffixflags fromrun - Aliases managed via
set -a,get -a,remove -ainstead of separatealiascommand - Type-aware ESLint linting with
recommendedTypeCheckedandstylisticTypeCheckedpresets
initcommand (replaced by first-run welcome message)- SQLite storage backend and
migratecommand codex_initMCP tool