Skip to content

Align codex-acp serde_json key ordering with codex CLI to avoid MCP keychain hash mismatches#155

Merged
benbrandt merged 1 commit intozed-industries:mainfrom
timvisher-dd:align-serd_json-ordering-with-codex-to-allow-keychain-access-for-mcp-servers-like-atlassian
Feb 16, 2026
Merged

Align codex-acp serde_json key ordering with codex CLI to avoid MCP keychain hash mismatches#155
benbrandt merged 1 commit intozed-industries:mainfrom
timvisher-dd:align-serd_json-ordering-with-codex-to-allow-keychain-access-for-mcp-servers-like-atlassian

Conversation

@timvisher-dd
Copy link
Copy Markdown
Contributor

@timvisher-dd timvisher-dd commented Feb 15, 2026

Codex MCP OAuth credential keys are derived from a JSON serialization that includes type, url, and headers. The hash changes if JSON object key order changes. Codex CLI binaries (Homebrew cask, GitHub release, and a local source build) preserve insertion order because codex-cli depends on codex-tui, and codex-tui enables serde_json/preserve_order. codex-acp does not depend on codex-tui, so it defaults to sorted key order and computes a different key hash.

This PR aligns codex-acp with codex CLI by enabling serde_json/preserve_order so the hashed key matches the CLI behavior and avoids keychain/account mismatches.

My biggest concern is that this alignment feels fragile but I'm not aware of any other way to do the alignment or fix the underlying issue.

Furthermore the entire approach of reusing the Agent's keychain entries feels a bit untenable to me. The UX of it is that because the -acp shim is the binary responsible for accessing the keychain item you get prompted to allow access to it from the binary. This is arguably correct but also arguably surprising and scary. I'm inclined to say that what I've built here is better than it not working at all but I'm open to other ideas about how to make the UX better. One I had is to somehow make it so that we use entirely separate keychain items than Codex or Claude so that the -acp binary itself is what's making and reading the items. The UX of that (if it's even possible) would be that you'd need to signin via acp if you wanted to use MCP from ACP.

Where the hash comes from

MCP OAuth credential keys are computed by serializing a JSON payload and hashing the result. The payload is built with keys type, url, and headers, then passed through sha_256_prefix:

The JSON string differs by feature:

  • Default serde_json (sorted keys):
    {"headers":{},"type":"http","url":"https://example.com/mcp"}
  • preserve_order (insertion order):
    {"type":"http","url":"https://example.com/mcp","headers":{}}

Those serialize to different hashes and therefore different keychain account IDs.

Why codex CLI preserves order

codex-tui enables serde_json/preserve_order, and codex-cli depends on codex-tui. Cargo feature unification makes preserve_order apply to the whole build:

codex-acp does not depend on codex-tui, so it does not inherit preserve_order unless explicitly enabled.

Evidence and repro

Black-box repro (no keychain / no Atlassian required)

The script x.cask-order-repro.sh seeds CODEX_HOME/.credentials.json with both possible keys and then runs codex mcp logout demo. The key that disappears indicates which JSON ordering was used to compute the hash.

x.cask-order-repro.sh

Usage examples:

  • Homebrew cask:
    CODEX_BIN=/opt/homebrew/bin/codex ./x.cask-order-repro.sh
  • GitHub release binary:
    CODEX_BIN=/path/to/codex-aarch64-apple-darwin ./x.cask-order-repro.sh
  • Local source build:
    CODEX_BIN=/path/to/codex-rs/target/debug/codex ./x.cask-order-repro.sh

Insertion-order key (preserve_order):

{"type":"http","url":"https://example.com/mcp","headers":{}}
-> demo|9a70b85417749d5c

Sorted-order key (default):

{"headers":{},"type":"http","url":"https://example.com/mcp"}
-> demo|6fe6427c8c9125c8

Observed behavior:

  • Homebrew cask codex 0.101.0 deletes demo|9a70b85417749d5c (preserve_order)
  • GitHub release binary rust-v0.101.0 deletes demo|9a70b85417749d5c (preserve_order)
  • Local source build (codex-rs) deletes demo|9a70b85417749d5c (preserve_order)

codex-acp behavior

A minimal test in tests/serde_json_ordering.rs shows codex-acp defaults to sorted order without preserve_order. Enabling serde_json/preserve_order in codex-acp flips the serialization to insertion order, matching codex CLI.

Change in this PR

Enable serde_json/preserve_order in codex-acp (Cargo.toml) to match codex CLI behavior and avoid mismatched MCP OAuth key hashes.

Co-authored-by: Codex <codex@openai.com>
@timvisher-dd timvisher-dd marked this pull request as ready for review February 15, 2026 22:20
Copy link
Copy Markdown
Member

@benbrandt benbrandt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@benbrandt benbrandt merged commit 23d8841 into zed-industries:main Feb 16, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants