{"slug": "unlock-computer-use-plugin-in-codex-app-for-eu-region-locked-accounts-in-place", "title": "Unlock Computer Use plugin in Codex.app for EU / region-locked accounts (in-place asar patch)", "summary": "OpenAI's Codex desktop app includes a Computer Use plugin binary that is pre-installed on all systems but hidden from users in regions like the EU through a server-side Statsig feature gate and a local configuration flag. This guide provides instructions to bypass both restrictions by editing the user's TOML config file and performing a binary patch on the Electron bundle to force the Statsig check to return true. The process takes approximately five minutes, requires intermediate technical skill, and includes automatic backups to minimize risk.", "body_md": "# Unlock Computer Use in Codex.app (EU / region-locked accounts)\n\nOpenAI's Codex desktop app ships with a **Computer Use** plugin (`computer-use@openai-bundled`) but hides it from users in regions where the feature isn't rolled out yet — including most of the EU. The plugin binary is already on your machine (`/Applications/Codex.app/Contents/Resources/plugins/openai-bundled/computer-use/`); only the UI is gated.\n\nThis guide flips two flags so the plugin appears in your plugin picker.\n\n> **Tested on:** macOS 15 (arm64), Codex.app version `26.506.31421` (May 2026 build).\n> **Difficulty:** Intermediate. You'll edit a TOML config and binary-patch Codex's Electron bundle.\n> **Risk:** Low. We back up everything we touch and never change file lengths.\n> **Time:** ~5 minutes.\n\n---\n\n## Why two flags?\n\nThe Codex client side gates `computer-use` behind two checks (both in `webview/assets/use-in-app-browser-use-availability-_UMFu9j2.js` inside `app.asar`):\n\n1. **Statsig feature gate `1506311413`** — server-evaluated. Returns `false` for EU/non-rolled-out accounts. This is the region wall.\n2. **Local experimental feature flag `computer_use`** — read from `~/.codex/config.toml` `[features]` section. Defaults off.\n\nWe enable both: edit the config (easy), and patch one 15-byte string inside `app.asar` so the Statsig call always returns truthy (less easy, but mechanical).\n\n---\n\n## Step 1 — Add the local feature flag\n\nOpen `~/.codex/config.toml` and add `computer_use = true` to your `[features]` block. If the block doesn't exist, create it.\n\n```toml\n[features]\ncomputer_use = true\n```\n\nThat's it for Step 1. Save the file.\n\n---\n\n## Step 2 — Add the plugin entry to your config\n\nCodex needs to know you actually want this plugin enabled. Append to `~/.codex/config.toml`:\n\n```toml\n[plugins.\"computer-use@openai-bundled\"]\nenabled = true\n```\n\n> **Note:** Codex's startup-sync may strip this entry if the Statsig gate still rejects the plugin. That's why Step 3 is required before Step 2 will stick.\n\n---\n\n## Step 3 — Patch the Statsig gate inside `app.asar`\n\nThis is the only invasive step. We do an **in-place** binary edit (no repacking, no extracting). The patch:\n- Replaces `s(\\`1506311413\\`)` → `(           !0)` (15 bytes → 15 bytes, evaluates to `true`)\n- Recomputes the SHA-256 of the patched file and overwrites the matching hash strings in the asar JSON header (same length, since SHA-256 hex is always 64 chars)\n- Recomputes the asar header SHA-256 and writes it to `Info.plist` `ElectronAsarIntegrity`\n- Ad-hoc re-signs the `.app` bundle so macOS hardened runtime accepts it\n\n### 3a. Install the helper\n\n```bash\nmkdir -p /tmp/codex-asar-tools && cd /tmp/codex-asar-tools\nnpm init -y >/dev/null\nnpm install @electron/asar\n```\n\n### 3b. Save the patcher script\n\nSave this as `/tmp/codex-asar-tools/patch.mjs`:\n\n```js\nimport * as asar from '@electron/asar';\nimport { readFileSync, writeFileSync, copyFileSync } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport { execSync } from 'node:child_process';\n\nconst APP        = '/Applications/Codex.app';\nconst ASAR       = `${APP}/Contents/Resources/app.asar`;\nconst PLIST      = `${APP}/Contents/Info.plist`;\nconst REL        = 'webview/assets/use-in-app-browser-use-availability-_UMFu9j2.js';\nconst BEFORE     = `s(\\`1506311413\\`)`;   // 15 bytes\nconst AFTER      = `(           !0)`;     // 15 bytes, evaluates !0 = true\nconst STAMP      = Date.now();\n\n// 0. Back up\ncopyFileSync(ASAR,  `${ASAR}.bak.${STAMP}`);\ncopyFileSync(PLIST, `${PLIST}.bak.${STAMP}`);\nconsole.log(`backups: *.bak.${STAMP}`);\n\n// 1. Parse asar layout from raw bytes (NOT from getRawHeader's headerSize, which is off by 8)\nconst fullBuf = readFileSync(ASAR);\nconst innerPickleTotal = fullBuf.readUInt32LE(4);\nconst stringLen        = fullBuf.readUInt32LE(12);\nconst headerJsonStart  = 16;\nconst headerJsonEnd    = headerJsonStart + stringLen;\nconst dataStart        = 8 + innerPickleTotal;        // <-- where file content blob begins\n\nconst header = JSON.parse(fullBuf.subarray(headerJsonStart, headerJsonEnd).toString('utf8'));\n\nfunction get(node, parts) {\n  for (const p of parts) {\n    if (!node.files || !node.files[p]) throw new Error('missing entry: ' + parts.join('/'));\n    node = node.files[p];\n  }\n  return node;\n}\nconst entry          = get(header, REL.split('/'));\nconst fileSize       = Number(entry.size);\nconst fileAbsOffset  = dataStart + Number(entry.offset);\n\n// 2. Read the file, swap the pattern, verify size\nconst orig = fullBuf.subarray(fileAbsOffset, fileAbsOffset + fileSize);\nconst text = orig.toString('utf8');\nif (!text.includes(BEFORE)) throw new Error('pattern not found — Codex version may have changed');\nif (BEFORE.length !== AFTER.length) throw new Error('replacement length mismatch');\nconst newBuf = Buffer.from(text.replace(BEFORE, AFTER), 'utf8');\n\n// 3. Sanity: pre-patch hash must match header\nconst preHash = createHash('sha256').update(orig).digest('hex');\nif (preHash !== entry.integrity.hash) throw new Error('pre-patch hash mismatch — file may already be patched or asar is corrupt');\n\n// 4. Compute new file integrity (single block since file < 4 MiB)\nconst newOverall = createHash('sha256').update(newBuf).digest('hex');\nconst newBlocks  = [];\nfor (let off = 0; off < newBuf.length; off += entry.integrity.blockSize) {\n  newBlocks.push(createHash('sha256').update(newBuf.subarray(off, off + entry.integrity.blockSize)).digest('hex'));\n}\n\n// 5. In-place edit: replace hash strings inside the JSON header\nfunction replaceInRange(big, [lo, hi], oldStr, newStr) {\n  const oldB = Buffer.from(oldStr, 'utf8');\n  const newB = Buffer.from(newStr, 'utf8');\n  if (oldB.length !== newB.length) throw new Error('hash length changed');\n  let pos = lo, count = 0;\n  while (pos < hi) {\n    const found = big.indexOf(oldB, pos);\n    if (found < 0 || found >= hi) break;\n    newB.copy(big, found);\n    count++;\n    pos = found + 1;\n  }\n  return count;\n}\nreplaceInRange(fullBuf, [headerJsonStart, headerJsonEnd], entry.integrity.hash, newOverall);\nfor (let i = 0; i < entry.integrity.blocks.length; i++) {\n  replaceInRange(fullBuf, [headerJsonStart, headerJsonEnd], entry.integrity.blocks[i], newBlocks[i]);\n}\n\n// 6. Patch file content at the correct offset\nnewBuf.copy(fullBuf, fileAbsOffset);\nwriteFileSync(ASAR, fullBuf);\n\n// 7. Recompute asar header hash for Info.plist\nconst newHeaderJson = readFileSync(ASAR).subarray(headerJsonStart, headerJsonEnd).toString('utf8');\nconst newHeaderHash = createHash('sha256').update(newHeaderJson).digest('hex');\nconsole.log('new asar header hash:', newHeaderHash);\n\n// 8. Update Info.plist\nexecSync(`/usr/libexec/PlistBuddy -c \"Set :ElectronAsarIntegrity:Resources/app.asar:hash ${newHeaderHash}\" \"${PLIST}\"`);\n\n// 9. Ad-hoc re-sign (Info.plist change invalidates Apple's signature)\nexecSync(`codesign --force --deep --sign - \"${APP}\"`, { stdio: 'inherit' });\n\nconsole.log('Done. Restart Codex.app.');\n```\n\n### 3c. Run it\n\n```bash\ncd /tmp/codex-asar-tools && node patch.mjs\n```\n\nExpected output (last lines):\n\n```\nnew asar header hash: e5bf56229175a64fea86dd21519c19b7b54a0014ac752016975c193bd9737385\n/Applications/Codex.app: replacing existing signature\nDone. Restart Codex.app.\n```\n\n### 3d. Restart Codex\n\n```bash\npkill -f \"/Applications/Codex.app\"\nopen -a Codex\n```\n\nOpen Settings → Plugins. **Computer Use** now appears next to Browser Use.\n\n---\n\n## How to verify it worked\n\n```bash\n# 1. Codex.app process is alive (no FATAL on launch)\nps aux | grep \"/Applications/Codex.app/Contents/MacOS/Codex\" | grep -v grep\n\n# 2. Check the patched bytes are present in app.asar\ngrep -ao '(           !0)' /Applications/Codex.app/Contents/Resources/app.asar | head -1\n\n# 3. Confirm Info.plist integrity hash matches the new header\n/usr/libexec/PlistBuddy -c \"Print :ElectronAsarIntegrity\" /Applications/Codex.app/Contents/Info.plist\n```\n\nIf Codex starts and the plugin tile is visible, you're done.\n\n---\n\n## Rollback\n\nThe patcher leaves backups at `/Applications/Codex.app/Contents/Resources/app.asar.bak.<timestamp>` and `/Applications/Codex.app/Contents/Info.plist.bak.<timestamp>`. To revert:\n\n```bash\n# Find the backup timestamps\nls /Applications/Codex.app/Contents/Resources/app.asar.bak.*\nls /Applications/Codex.app/Contents/Info.plist.bak.*\n\n# Restore (replace TS with the timestamp from above)\nTS=1778355270\ncp \"/Applications/Codex.app/Contents/Resources/app.asar.bak.$TS\"   /Applications/Codex.app/Contents/Resources/app.asar\ncp \"/Applications/Codex.app/Contents/Info.plist.bak.$TS\"           /Applications/Codex.app/Contents/Info.plist\ncodesign --force --deep --sign - /Applications/Codex.app\n```\n\nYou can also just remove `[plugins.\"computer-use@openai-bundled\"]` and `computer_use = true` from `~/.codex/config.toml` if you want to disable the feature without unpatching.\n\n---\n\n## Pitfalls (what NOT to do)\n\nThese are the dead ends I hit before finding the working approach. Skip them.\n\n### Don't repack the asar\n\n`@electron/asar pack` looks like the obvious tool, but Codex marks `node_modules/{better-sqlite3,node-pty,objc-js}` as **unpacked** (their native `.node` binaries live in `app.asar.unpacked/`, not inside the asar). A naive repack pulls those files back into the asar; Codex then exits at startup with:\n\n```\nCodex failed to start.\nbetter-sqlite3 is only bundled with the Electron app\n```\n\nIn-place editing avoids this entirely.\n\n### Don't trust `getRawHeader().headerSize` as the data offset\n\n`@electron/asar`'s `getRawHeader()` returns `headerSize` equal to the **inner pickle size** (e.g. `371956`). The actual file content blob starts **8 bytes later** (after the outer pickle preamble), at byte `8 + innerPickleTotal` (e.g. `371964`).\n\nIf you use `headerSize` as the data start, your read/write offsets are off by 8. The content modification still lands correctly (read and write both use the same wrong base, so they cancel), but the SHA-256 you compute is over the wrong bytes — Electron then fails block validation:\n\n```\nFATAL:asar_file_validator.cc:129] Failed to validate block while ending ASAR file stream: 0\n```\n\nThe patcher above parses the pickle preamble manually to avoid this.\n\n### Don't skip the asar integrity update\n\nModern Electron embeds per-file SHA-256 hashes in the asar JSON header (`integrity.hash` and `integrity.blocks[]`) and a top-level header hash in `Info.plist:ElectronAsarIntegrity`. Both must be updated:\n\n- **File hash** lives inside the JSON header. Updating it does not change header byte length (SHA-256 hex is always 64 chars).\n- **Header hash** is `sha256(headerJsonBytes)` and goes into `Info.plist`.\n\nSkip either and you get the FATAL above.\n\n### Don't skip the ad-hoc resign\n\nEditing `Info.plist` invalidates Apple's code signature. Without `codesign --force --deep --sign -`, the app may refuse to launch under hardened runtime. Ad-hoc signing (`-`) is enough — you don't need a Developer ID.\n\n---\n\n## Auto-update warning\n\nCodex auto-updates via Sparkle. **A new version will overwrite `app.asar` and undo the patch.** Re-run `node /tmp/codex-asar-tools/patch.mjs` after each update.\n\nTo make this less annoying, save `patch.mjs` somewhere durable (your dotfiles repo, `~/bin/`, etc.) and consider wrapping it in a launchd agent that watches the version string in `Info.plist` and re-runs on change.\n\nIf a future Codex release renames or rewrites `use-in-app-browser-use-availability-_UMFu9j2.js`, the patcher will exit with `pattern not found — Codex version may have changed`. You'll need to re-locate the new bundle filename and the new Statsig gate ID by:\n\n1. Extracting `app.asar` to a temp dir (`npx @electron/asar extract /Applications/Codex.app/Contents/Resources/app.asar /tmp/extracted`)\n2. Grepping for `featureName` and `computer_use` in `webview/assets/*.js`\n3. Identifying the gate ID literal passed to the Statsig hook (e.g. `s(\\`<digits>\\`)`)\n\n---\n\n## Why this works\n\nThe Codex Electron client's plugin picker filters out `computer-use@openai-bundled` when **either** the Statsig gate `1506311413` returns `false` **or** the local experimental feature `computer_use` is unset. Both paths are inside `webview/assets/use-in-app-browser-use-availability-_UMFu9j2.js` in a function that returns `{ available, isFetching, isLoading }`. By forcing the Statsig call to evaluate truthy and setting the local flag, `available` becomes `true` and the plugin tile renders.\n\nThe plugin binary itself ships with the app already — no download needed, no API key, no auth bypass. The server-side `plugin/list` JSON-RPC method already returns `computer-use` correctly even on EU accounts (verified by directly driving the app-server stdio interface). The gating is purely client-side.\n\n---\n\n## Credits\n\nReverse-engineered from a fresh Codex.app extraction in May 2026, building on the Codex plugin/marketplace architecture documented in [this gist](https://gist.github.com/clairernovotny/89587e4932d854b10bbab913b95ecb5c).\n\nUse at your own risk. Not affiliated with OpenAI.\n", "url": "https://wpnews.pro/news/unlock-computer-use-plugin-in-codex-app-for-eu-region-locked-accounts-in-place", "canonical_source": "https://gist.github.com/Saik0s/6d098d9caae564f48e4decfeaea67f1d", "published_at": "2026-05-09 20:58:20+00:00", "updated_at": "2026-05-21 21:06:10.872017+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "products"], "entities": ["OpenAI", "Codex", "EU", "Statsig", "macOS"], "alternates": {"html": "https://wpnews.pro/news/unlock-computer-use-plugin-in-codex-app-for-eu-region-locked-accounts-in-place", "markdown": "https://wpnews.pro/news/unlock-computer-use-plugin-in-codex-app-for-eu-region-locked-accounts-in-place.md", "text": "https://wpnews.pro/news/unlock-computer-use-plugin-in-codex-app-for-eu-region-locked-accounts-in-place.txt", "jsonld": "https://wpnews.pro/news/unlock-computer-use-plugin-in-codex-app-for-eu-region-locked-accounts-in-place.jsonld"}}