claude-multiacc 2.0.61 → 2.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +6 -2
  2. package/bin/claude-accounts +4 -4
  3. package/bin/codex +54 -13
  4. package/bin/codex-accounts +28 -7
  5. package/docs/ACCOUNT_OPERATIONS.md +2 -2
  6. package/docs/AUTORESUME.md +16 -2
  7. package/docs/CODEX.md +58 -2
  8. package/docs/CODEX_MCP_AUTH.md +58 -0
  9. package/lib/__pycache__/audit.cpython-314.pyc +0 -0
  10. package/lib/__pycache__/autoresume.cpython-314.pyc +0 -0
  11. package/lib/__pycache__/autoresume_cross.cpython-314.pyc +0 -0
  12. package/lib/__pycache__/autoresume_cross_dispatch.cpython-314.pyc +0 -0
  13. package/lib/__pycache__/autoresume_cross_receipt.cpython-314.pyc +0 -0
  14. package/lib/__pycache__/autoresume_handoff.cpython-314.pyc +0 -0
  15. package/lib/__pycache__/autoresume_probe.cpython-314.pyc +0 -0
  16. package/lib/__pycache__/autoresume_receipt.cpython-314.pyc +0 -0
  17. package/lib/__pycache__/claude_client_error.cpython-314.pyc +0 -0
  18. package/lib/__pycache__/claude_reset.cpython-314.pyc +0 -0
  19. package/lib/__pycache__/claude_token_identity.cpython-314.pyc +0 -0
  20. package/lib/__pycache__/codex_autoresume_mode.cpython-314.pyc +0 -0
  21. package/lib/__pycache__/codex_config_edit.cpython-314.pyc +0 -0
  22. package/lib/__pycache__/codex_credit_controls.cpython-314.pyc +0 -0
  23. package/lib/__pycache__/codex_credit_local_scope.cpython-314.pyc +0 -0
  24. package/lib/__pycache__/codex_credit_selection.cpython-314.pyc +0 -0
  25. package/lib/__pycache__/codex_credits.cpython-314.pyc +0 -0
  26. package/lib/__pycache__/codex_limits_lock.cpython-314.pyc +0 -0
  27. package/lib/__pycache__/codex_mcp_oauth.cpython-314.pyc +0 -0
  28. package/lib/__pycache__/codex_python.cpython-314.pyc +0 -0
  29. package/lib/__pycache__/codex_resume_cwd.cpython-314.pyc +0 -0
  30. package/lib/__pycache__/codex_speed.cpython-314.pyc +0 -0
  31. package/lib/__pycache__/codex_trust.cpython-314.pyc +0 -0
  32. package/lib/__pycache__/codex_usage_fetch.cpython-314.pyc +0 -0
  33. package/lib/__pycache__/keychain.cpython-314.pyc +0 -0
  34. package/lib/__pycache__/mcp_mirror.cpython-314.pyc +0 -0
  35. package/lib/__pycache__/mcp_oauth.cpython-314.pyc +0 -0
  36. package/lib/__pycache__/mcp_registry.cpython-314.pyc +0 -0
  37. package/lib/__pycache__/model_catalog.cpython-314.pyc +0 -0
  38. package/lib/__pycache__/model_match.cpython-314.pyc +0 -0
  39. package/lib/__pycache__/report.cpython-314.pyc +0 -0
  40. package/lib/__pycache__/selector_credit_scope.cpython-314.pyc +0 -0
  41. package/lib/__pycache__/selector_credits.cpython-314.pyc +0 -0
  42. package/lib/__pycache__/selector_funding.cpython-314.pyc +0 -0
  43. package/lib/__pycache__/selector_policy.cpython-314.pyc +0 -0
  44. package/lib/__pycache__/selector_primitives.cpython-314.pyc +0 -0
  45. package/lib/__pycache__/session_settings.cpython-314.pyc +0 -0
  46. package/lib/__pycache__/shim_path.cpython-314.pyc +0 -0
  47. package/lib/autoresume.py +34 -13
  48. package/lib/autoresume_cross.py +2 -1
  49. package/lib/autoresume_handoff.py +13 -5
  50. package/lib/codex_exec_stdin.sh +45 -0
  51. package/lib/codex_mcp_oauth.py +204 -0
  52. package/lib/codex_mcp_oauth.sh +29 -0
  53. package/lib/codex_mcp_oauth_locks.py +158 -0
  54. package/lib/codex_mcp_oauth_native.py +102 -0
  55. package/lib/codex_mcp_oauth_store.py +149 -0
  56. package/lib/codex_resume_cwd.py +57 -0
  57. package/lib/codex_retry_process.sh +37 -0
  58. package/lib/codex_speed.py +351 -0
  59. package/lib/codex_speed.sh +39 -0
  60. package/lib/common.sh +16 -3
  61. package/lib/model_match.py +4 -1
  62. package/lib/session_settings.py +25 -6
  63. package/lib/shim_path.py +6 -0
  64. package/package.json +10 -5
  65. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-314.pyc +0 -0
  66. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-314.pyc +0 -0
  67. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-314.pyc +0 -0
  68. package/tests/__pycache__/autoresume_cross_unit_support.cpython-314.pyc +0 -0
  69. package/tests/__pycache__/autoresume_tmux_support.cpython-314.pyc +0 -0
  70. package/tests/__pycache__/packaged_command_support.cpython-314.pyc +0 -0
  71. package/tests/__pycache__/test_autoresume.cpython-314.pyc +0 -0
  72. package/tests/__pycache__/test_autoresume_catalog.cpython-314.pyc +0 -0
  73. package/tests/__pycache__/test_autoresume_catalog_cli.cpython-314.pyc +0 -0
  74. package/tests/__pycache__/test_autoresume_codex_records.cpython-314.pyc +0 -0
  75. package/tests/__pycache__/test_autoresume_codex_trust.cpython-314.pyc +0 -0
  76. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-314.pyc +0 -0
  77. package/tests/__pycache__/test_autoresume_cross_guards.cpython-314.pyc +0 -0
  78. package/tests/__pycache__/test_autoresume_cross_holders.cpython-314.pyc +0 -0
  79. package/tests/__pycache__/test_autoresume_cross_origin.cpython-314.pyc +0 -0
  80. package/tests/__pycache__/test_autoresume_cross_policy.cpython-314.pyc +0 -0
  81. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-314.pyc +0 -0
  82. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-314.pyc +0 -0
  83. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-314.pyc +0 -0
  84. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-314.pyc +0 -0
  85. package/tests/__pycache__/test_autoresume_cursor.cpython-314.pyc +0 -0
  86. package/tests/__pycache__/test_autoresume_cwd.cpython-314.pyc +0 -0
  87. package/tests/__pycache__/test_autoresume_cwd_handoff.cpython-314.pyc +0 -0
  88. package/tests/__pycache__/test_autoresume_cwd_reader.cpython-314.pyc +0 -0
  89. package/tests/__pycache__/test_autoresume_cwd_tmux.cpython-314.pyc +0 -0
  90. package/tests/__pycache__/test_autoresume_fallback.cpython-314.pyc +0 -0
  91. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-314.pyc +0 -0
  92. package/tests/__pycache__/test_autoresume_handoff_history.cpython-314.pyc +0 -0
  93. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-314.pyc +0 -0
  94. package/tests/__pycache__/test_autoresume_memory.cpython-314.pyc +0 -0
  95. package/tests/__pycache__/test_autoresume_memory_tmux.cpython-314.pyc +0 -0
  96. package/tests/__pycache__/test_autoresume_mode.cpython-314.pyc +0 -0
  97. package/tests/__pycache__/test_autoresume_probe.cpython-314.pyc +0 -0
  98. package/tests/__pycache__/test_autoresume_receipt.cpython-314.pyc +0 -0
  99. package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-314.pyc +0 -0
  100. package/tests/__pycache__/test_autoresume_settings.cpython-314.pyc +0 -0
  101. package/tests/__pycache__/test_autoresume_sidechain.cpython-314.pyc +0 -0
  102. package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-314.pyc +0 -0
  103. package/tests/__pycache__/test_autoresume_tmux.cpython-314.pyc +0 -0
  104. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-314.pyc +0 -0
  105. package/tests/__pycache__/test_autoresume_trust_tmux.cpython-314.pyc +0 -0
  106. package/tests/__pycache__/test_claude_reset.cpython-314.pyc +0 -0
  107. package/tests/__pycache__/test_claude_token_identity.cpython-314.pyc +0 -0
  108. package/tests/__pycache__/test_codex_exec_stdin.cpython-314.pyc +0 -0
  109. package/tests/__pycache__/test_codex_mcp_oauth.cpython-314.pyc +0 -0
  110. package/tests/__pycache__/test_codex_reset.cpython-314.pyc +0 -0
  111. package/tests/__pycache__/test_codex_reset_credits.cpython-314.pyc +0 -0
  112. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-314.pyc +0 -0
  113. package/tests/__pycache__/test_codex_reset_credits_report.cpython-314.pyc +0 -0
  114. package/tests/__pycache__/test_codex_reset_fetch.cpython-314.pyc +0 -0
  115. package/tests/__pycache__/test_codex_reset_polling.cpython-314.pyc +0 -0
  116. package/tests/__pycache__/test_codex_reset_reporting.cpython-314.pyc +0 -0
  117. package/tests/__pycache__/test_codex_reset_windows.cpython-314.pyc +0 -0
  118. package/tests/__pycache__/test_mcp_mirror.cpython-314.pyc +0 -0
  119. package/tests/__pycache__/test_mcp_mirror_args.cpython-314.pyc +0 -0
  120. package/tests/__pycache__/test_mcp_oauth.cpython-314.pyc +0 -0
  121. package/tests/__pycache__/test_mcp_registry.cpython-314.pyc +0 -0
  122. package/tests/__pycache__/test_selector.cpython-314.pyc +0 -0
  123. package/tests/__pycache__/test_selector_cached_credits.cpython-314.pyc +0 -0
  124. package/tests/__pycache__/test_selector_credit_scope.cpython-314.pyc +0 -0
  125. package/tests/__pycache__/test_selector_credit_shim.cpython-314.pyc +0 -0
  126. package/tests/__pycache__/test_selector_credits.cpython-314.pyc +0 -0
  127. package/tests/__pycache__/test_selector_spread.cpython-314.pyc +0 -0
  128. package/tests/__pycache__/test_token_identity_ceremony.cpython-314.pyc +0 -0
  129. package/tests/__pycache__/test_token_identity_integration.cpython-314.pyc +0 -0
  130. package/tests/__pycache__/test_token_identity_retry.cpython-314.pyc +0 -0
  131. package/tests/__pycache__/test_token_identity_sync.cpython-314.pyc +0 -0
  132. package/tests/fake_codex_tui.py +23 -1
  133. package/tests/run-tests.sh +23 -7
  134. package/tests/test_autoresume_cwd.py +71 -0
  135. package/tests/test_autoresume_cwd_handoff.py +92 -0
  136. package/tests/test_autoresume_cwd_reader.py +105 -0
  137. package/tests/test_autoresume_cwd_tmux.py +54 -0
  138. package/tests/test_codex_exec_stdin.py +178 -0
  139. package/tests/test_codex_mcp_oauth.py +146 -0
  140. package/tests/test_codex_mcp_oauth_adopt.py +87 -0
  141. package/tests/test_codex_mcp_oauth_migration.py +141 -0
  142. package/tests/test_codex_mcp_oauth_native.py +109 -0
  143. package/tests/test_codex_mcp_oauth_safety.py +130 -0
  144. package/tests/test_codex_mcp_oauth_shim.py +188 -0
  145. package/tests/test_codex_retry_process.py +143 -0
  146. package/tests/test_codex_speed.py +236 -0
  147. package/tests/test_mutate_lock.py +107 -0
  148. package/tests/test_shim_path.py +44 -4
  149. package/lib/__pycache__/audit.cpython-312.pyc +0 -0
  150. package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
  151. package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
  152. package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
  153. package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
  154. package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
  155. package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
  156. package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
  157. package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
  158. package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
  159. package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
  160. package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
  161. package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
  162. package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
  163. package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
  164. package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
  165. package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
  166. package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
  167. package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
  168. package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
  169. package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
  170. package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
  171. package/lib/__pycache__/mcp_mirror.cpython-312.pyc +0 -0
  172. package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
  173. package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
  174. package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
  175. package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
  176. package/lib/__pycache__/report.cpython-312.pyc +0 -0
  177. package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
  178. package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
  179. package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
  180. package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
  181. package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
  182. package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
  183. package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
  184. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
  185. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
  186. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
  187. package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
  188. package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
  189. package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
  190. package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
  191. package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
  192. package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
  193. package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
  194. package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
  195. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
  196. package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
  197. package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
  198. package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
  199. package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
  200. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
  201. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
  202. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
  203. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
  204. package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
  205. package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
  206. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
  207. package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
  208. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
  209. package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
  210. package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
  211. package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
  212. package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
  213. package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
  214. package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
  215. package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
  216. package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
  217. package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
  218. package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
  219. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
  220. package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
  221. package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
  222. package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
  223. package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
  224. package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
  225. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
  226. package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
  227. package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
  228. package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
  229. package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
  230. package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
  231. package/tests/__pycache__/test_mcp_mirror.cpython-312.pyc +0 -0
  232. package/tests/__pycache__/test_mcp_mirror_args.cpython-312.pyc +0 -0
  233. package/tests/__pycache__/test_mcp_oauth.cpython-312.pyc +0 -0
  234. package/tests/__pycache__/test_mcp_registry.cpython-312.pyc +0 -0
  235. package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
  236. package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
  237. package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
  238. package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
  239. package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
  240. package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
  241. package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
  242. package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
  243. package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
  244. package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
package/README.md CHANGED
@@ -685,8 +685,12 @@ claude-accounts mcp auth-forget higgsfield # log it out of EVERY account on th
685
685
  cannot open) stays ignored and is cleared by a later pass.
686
686
  - **Kill switches**: `CLAUDE_MULTIACC_MCP_OAUTH=0` stops sharing (the shim's merge and the
687
687
  limits pass's sync); `CLAUDE_MULTIACC_MCP_OAUTH_REFRESH=0` never refreshes — and so shares
688
- nothing. `auth-status` and `auth-forget` always run. Claude pool only: Codex keeps MCP
689
- OAuth in its own store, and `codex-accounts mcp auth-…` says so.
688
+ nothing. `auth-status` and `auth-forget` always run. These commands describe the Claude pool.
689
+
690
+ Codex shares its native MCP credential file and refresh locks locally, so a login in
691
+ one pooled account works in the others. Run `codex-accounts mcp auth-sync` to repair
692
+ sharing now; use native `codex mcp login NAME`, `list`, and `logout NAME` for authentication.
693
+ See [Codex MCP authentication](docs/CODEX_MCP_AUTH.md) for storage constraints and recovery.
690
694
 
691
695
  app-robot tasks are different: the panel launches Claude with `--strict-mcp-config` and a
692
696
  merged MCP document (`agent_mcp.servers_for`) and Codex with the same document as `-c`
@@ -1708,7 +1708,7 @@ token_identity_receipt() { # Unknown results must never overwrite a peer's prove
1708
1708
  limits_distribute_now() {
1709
1709
  local lock="$ACC_ROOT/tmp/limits-push.lock"
1710
1710
  mkdir -p "$ACC_ROOT/tmp" 2>/dev/null || return 0
1711
- if ! mkdir "$lock" 2>/dev/null; then
1711
+ if ! atomic_mkdir "$lock" 2>/dev/null; then
1712
1712
  # A DETACHED push that is killed (logout, reboot, pkill) never runs its EXIT trap,
1713
1713
  # and mkdir can never take a lock dir nobody will remove: one stranded lock
1714
1714
  # silently stopped ALL telemetry distribution on the live pool from 2026-09-03
@@ -1721,7 +1721,7 @@ limits_distribute_now() {
1721
1721
  # exactly as before.
1722
1722
  [ $(( $(epoch_now) - $(file_mtime "$lock") )) -gt 600 ] || return 0
1723
1723
  rm -rf "$lock" 2>/dev/null
1724
- mkdir "$lock" 2>/dev/null || return 0
1724
+ atomic_mkdir "$lock" 2>/dev/null || return 0
1725
1725
  log_to sync.log "stale limits-push lock broken (older than 600s); distributing"
1726
1726
  fi
1727
1727
  trap 'rmdir "$lock" 2>/dev/null || true' EXIT
@@ -1792,7 +1792,7 @@ cmd_limits() {
1792
1792
  done
1793
1793
  local lock="$ACC_ROOT/.locks/limits"
1794
1794
  mkdir -p "$ACC_ROOT/.locks"
1795
- if ! mkdir "$lock" 2>/dev/null; then
1795
+ if ! atomic_mkdir "$lock" 2>/dev/null; then
1796
1796
  local age=$(( $(epoch_now) - $(file_mtime "$lock") ))
1797
1797
  if [ "$age" -lt 120 ]; then
1798
1798
  # A --json caller still gets the document (built from the state on disk) —
@@ -1802,7 +1802,7 @@ cmd_limits() {
1802
1802
  return 0
1803
1803
  fi
1804
1804
  rm -rf "$lock"
1805
- if ! mkdir "$lock" 2>/dev/null; then
1805
+ if ! atomic_mkdir "$lock" 2>/dev/null; then
1806
1806
  if [ "$json" = "1" ]; then emit_report_json limits; return $?; fi
1807
1807
  return 0
1808
1808
  fi
package/bin/codex CHANGED
@@ -93,6 +93,11 @@ fi
93
93
  # the servers themselves. Same contract as bin/claude's mcp_ensure.
94
94
  MCP_PY="$SELF_DIR/../lib/mcp_registry.py"
95
95
  MCP_PYBIN="${CLAUDE_MULTIACC_PYTHON:-python3}"
96
+ mcp_oauth_ensure() {
97
+ [ -f "$SELF_DIR/../lib/codex_mcp_oauth.sh" ] || return 0
98
+ bash "$SELF_DIR/../lib/codex_mcp_oauth.sh" "$ACC_ROOT" "$MCP_PYBIN" \
99
+ "$SELF_DIR/../lib/codex_mcp_oauth.py" "$1" "$REAL" >/dev/null 2>&1 </dev/null || true
100
+ }
96
101
  mcp_sig() { # $1 acct dir -> "m:z,m:z,m:z" (registry, overlay, account config; - = absent)
97
102
  local f out="" s
98
103
  for f in "$ACC_ROOT/mcp-servers.json" "$ACC_ROOT/mcp-servers.local.json" "$1/config.toml"; do
@@ -151,6 +156,20 @@ cli_positional_at() { # argv -> prints the index of its first positional argumen
151
156
  return 1
152
157
  }
153
158
 
159
+ # Account/model capabilities are resolved only after the serving account is known.
160
+ # The helper is optional so a damaged install retains the shim's fail-open contract.
161
+ codex_native_argv() {
162
+ CODEX_SPEED_ARGV=("$@")
163
+ if [ -f "$SELF_DIR/../lib/codex_speed.sh" ]; then
164
+ . "$SELF_DIR/../lib/codex_speed.sh"
165
+ codex_speed_prepare "$@" || return $?
166
+ fi
167
+ }
168
+ codex_native_exec() {
169
+ codex_native_argv "$@" || exit $?
170
+ exec "$REAL" ${CODEX_SPEED_ARGV[@]+"${CODEX_SPEED_ARGV[@]}"}
171
+ }
172
+
154
173
  # Whether argv is `codex [global options] mcp add|remove …`.
155
174
  mcp_mutation_argv() {
156
175
  local i j
@@ -243,8 +262,11 @@ if [ -n "${CODEX_HOME:-}" ] \
243
262
  printf 'codex-multiacc: auto-resume cannot enter account selection.\n' >&2
244
263
  exit 2
245
264
  fi
265
+ if [ "${CODEX_MULTIACC_DISABLE:-0}" != "1" ] && [ -n "${CODEX_HOME:-}" ]; then
266
+ mcp_oauth_ensure "$CODEX_HOME"
267
+ fi
246
268
  mcp_nested_mirror "$@" || true
247
- exec "$REAL" "$@"
269
+ codex_native_exec "$@"
248
270
  fi
249
271
 
250
272
  now="$(date +%s)"
@@ -1066,6 +1088,9 @@ ar_argv_ok() { # the argv codex is about to get
1066
1088
  model:*) case "$a" in ''|-*) return 1 ;; esac ;;
1067
1089
  effort:model_reasoning_effort=?*)
1068
1090
  case "${a#model_reasoning_effort=}" in *[!$AR_LOWER]*) return 1 ;; esac ;;
1091
+ effort:service_tier=*)
1092
+ case "${a#service_tier=}" in default|fast|priority|ultrafast|'"default"'|'"fast"'|'"priority"'|'"ultrafast"'|"'default'"|"'fast'"|"'priority'"|"'ultrafast'") ;; *) return 1 ;; esac ;;
1093
+ effort:features.fast_mode=true) ;;
1069
1094
  *) return 1 ;;
1070
1095
  esac
1071
1096
  want=""
@@ -1258,11 +1283,13 @@ if [ -n "${CODEX_ACCOUNT:-}" ] && [ "$AR_PROBE" != "1" ]; then
1258
1283
  share_state_index "$d"
1259
1284
  export CODEX_HOME="$d"
1260
1285
  export CODEX_SHIM_ACTIVE=1
1286
+ unset CODEX_MULTIACC_SELECTED_HOME
1261
1287
  # A pinned account carries every registered MCP server too, and a pinned
1262
1288
  # `codex mcp add|remove` is mirrored like any other (exits inside).
1263
1289
  mcp_ensure "$d"
1290
+ mcp_oauth_ensure "$d"
1264
1291
  mcp_mirror_run "$d" "$@" || true
1265
- exec "$REAL" "$@"
1292
+ codex_native_exec "$@"
1266
1293
  fi
1267
1294
  sel_log "pin-invalid account=$CODEX_ACCOUNT (no such dir; random fallback)"
1268
1295
  fi
@@ -1333,7 +1360,7 @@ if [ "${#valid[@]}" -eq 0 ]; then
1333
1360
  [ -t 2 ] && printf 'codex-multiacc: no pool account is usable (%s) — see: codex-accounts expired, then: codex-accounts relogin\n' \
1334
1361
  "${ids# }" >&2
1335
1362
  fi
1336
- exec "$REAL" "$@"
1363
+ codex_native_exec "$@"
1337
1364
  fi
1338
1365
 
1339
1366
  # ---- rotation ----------------------------------------------------------------
@@ -1551,6 +1578,7 @@ if [ "$AR_TIER" = "credits" ]; then
1551
1578
  fi
1552
1579
  # The picked account carries every registered MCP server before the client starts.
1553
1580
  mcp_ensure "$pick"
1581
+ mcp_oauth_ensure "$pick"
1554
1582
 
1555
1583
  # Opportunistic limits refresh: non-blocking, throttled, backgrounded. The windows are
1556
1584
  # deliberately wide (10m, matching the 5m scheduled pass): the usage endpoint rate-limits
@@ -1581,6 +1609,9 @@ sel_log "$acct weekly=$(fresh_field "$pick" weekly_percent || echo '?')%" \
1581
1609
  "session-ok=${PICK_GATE_COUNT} pwd=$PWD${credit_funding_log}"
1582
1610
 
1583
1611
  export CODEX_SHIM_ACTIVE=1
1612
+ # An isolated child job can distinguish this automatic selection from a caller pin.
1613
+ # Consumers must also require CODEX_HOME to match and respect explicit pins/disable.
1614
+ export CODEX_MULTIACC_SELECTED_HOME="$pick"
1584
1615
 
1585
1616
  # A `codex mcp add|remove` is run as a child under the picked account and mirrored to
1586
1617
  # every other one (exits inside); anything else falls through to exec.
@@ -1591,9 +1622,9 @@ if mcp_mutation_argv "$@"; then
1591
1622
  fi
1592
1623
 
1593
1624
  # Auto-retry applies only to `codex exec` runs with an alternative account available,
1594
- # and only when stdin is finite (tty, regular file, or char device like /dev/null).
1595
- # A service-spawned pipe that never EOFs must take the plain exec path, or the
1596
- # stdin pre-buffering below would hang the call.
1625
+ # and stdin is a file/device or an explicit `exec ... -` prompt. That prompt needs
1626
+ # EOF in the native CLI too. Other pipes stay unbuffered so a non-consuming command
1627
+ # can finish even when its service leaves stdin open.
1597
1628
  wants_retry=0
1598
1629
  if [ "${CODEX_SHIM_RETRY:-1}" != "0" ] \
1599
1630
  && { [ "${#eligible[@]}" -ge 2 ] || [ "$AR_TIER" = "credits" ]; }; then
@@ -1603,19 +1634,26 @@ if [ "${CODEX_SHIM_RETRY:-1}" != "0" ] \
1603
1634
  if [ "$wants_retry" = "1" ]; then
1604
1635
  # A TTY cannot be buffered or replayed: `codex exec` with no prompt argument reads
1605
1636
  # stdin, and the retry path would hand it /dev/null. Plain exec instead —
1606
- # stdin is inherited untouched. A pipe that never EOFs would hang the pre-buffer,
1607
- # so only finite stdin (regular file, /dev/null-style char device) takes the retry
1608
- # path; everything else execs directly.
1637
+ # stdin is inherited untouched. Explicit stdin prompts can replay a pipe: an
1638
+ # AppInspire/Node child receives a complete prompt through a socket, not a file.
1639
+ # Keep unknown commands and pipes the CLI need not consume on direct exec.
1609
1640
  if [ -t 0 ]; then
1610
1641
  wants_retry=0
1611
1642
  elif [ -f /dev/fd/0 ] || [ -c /dev/fd/0 ]; then
1612
1643
  :
1644
+ elif bash "$SELF_DIR/../lib/codex_exec_stdin.sh" "$@" 2>/dev/null; then
1645
+ :
1613
1646
  else
1614
1647
  wants_retry=0
1615
1648
  fi
1616
1649
  fi
1617
1650
  fi
1618
1651
 
1652
+ # Keep a damaged installation fail-open rather than wrapping an unowned child.
1653
+ if [ "$wants_retry" = 1 ] && ! . "$SELF_DIR/../lib/codex_retry_process.sh" 2>/dev/null; then
1654
+ wants_retry=0
1655
+ fi
1656
+
1619
1657
  share_state_index "$pick"
1620
1658
  if [ "$wants_retry" = "0" ]; then
1621
1659
  export CODEX_HOME="$pick"
@@ -1636,14 +1674,14 @@ if [ "$wants_retry" = "0" ]; then
1636
1674
  fi
1637
1675
  ar_scrub_env
1638
1676
  [ "$AR_NO_DAEMON" != 1 ] || set -- --no-daemon "$@"
1639
- exec "$REAL" "$@"
1677
+ codex_native_exec "$@"
1640
1678
  fi
1641
1679
 
1642
1680
  # Retry path: buffer stdio so a retried call never double-emits partial output.
1643
1681
  mkdir -p "$ACC_ROOT/tmp" 2>/dev/null || true
1644
1682
  tmpd="$(mktemp -d "$ACC_ROOT/tmp/shim.XXXXXX" 2>/dev/null)" || {
1645
1683
  export CODEX_HOME="$pick"
1646
- exec "$REAL" "$@"
1684
+ codex_native_exec "$@"
1647
1685
  }
1648
1686
  trap 'rm -rf "$tmpd"' EXIT
1649
1687
 
@@ -1652,7 +1690,7 @@ trap 'rm -rf "$tmpd"' EXIT
1652
1690
  # nonzero — a hard failure. Verify now, fall back to plain exec if we cannot.
1653
1691
  if ! : > "$tmpd/out" 2>/dev/null || ! : > "$tmpd/err" 2>/dev/null; then
1654
1692
  export CODEX_HOME="$pick"
1655
- exec "$REAL" "$@"
1693
+ codex_native_exec "$@"
1656
1694
  fi
1657
1695
 
1658
1696
  stdin_file=""
@@ -1687,7 +1725,9 @@ cur="$pick"
1687
1725
  rc=0
1688
1726
  while :; do
1689
1727
  if [ -n "$stdin_file" ]; then exec 3< "$stdin_file"; else exec 3< /dev/null; fi
1690
- CODEX_HOME="$cur" "$REAL" "$@" <&3 > "$tmpd/out" 2> "$tmpd/err"
1728
+ CODEX_HOME="$cur" codex_native_argv "$@" || exit $?
1729
+ CODEX_HOME="$cur" CODEX_MULTIACC_SELECTED_HOME="$cur" \
1730
+ codex_retry_run "$REAL" ${CODEX_SPEED_ARGV[@]+"${CODEX_SPEED_ARGV[@]}"} <&3 > "$tmpd/out" 2> "$tmpd/err"
1691
1731
  rc=$?
1692
1732
  exec 3<&-
1693
1733
  if [ "$rc" -ne 0 ] && [ "$attempt" -eq 1 ] \
@@ -1749,6 +1789,7 @@ while :; do
1749
1789
  # away from — not the one that bounced.
1750
1790
  remember_pick "$cur"
1751
1791
  mcp_ensure "$cur"
1792
+ mcp_oauth_ensure "$cur"
1752
1793
  attempt=2
1753
1794
  continue
1754
1795
  fi
@@ -106,6 +106,10 @@ USAGE
106
106
  every install and self-update runs it, and each limits pass refreshes its own pool's
107
107
  copy once it is an hour old. --if-stale leaves a younger copy alone; --quiet prints
108
108
  nothing. show prints the merged catalog.
109
+ codex-accounts speed --account-dir DIR --model MODEL --speed normal|fast|ultrafast [--json]
110
+ resolve speed from this account's native model catalog. Ultrafast verifies
111
+ account access through native model/list without inference; unknown or
112
+ unavailable access falls back to Fast. Never changes account configuration.
109
113
  codex-accounts post-sync (server side) seed dirs, fix perms, quick verify
110
114
  codex-accounts mcp add <name> [--provider claude|codex|both] [--scope user]
111
115
  [-e KEY=VAL]... [-H 'Header: v']... [--transport stdio|http]
@@ -133,6 +137,8 @@ USAGE
133
137
  re-apply the registry now (repair drift)
134
138
  codex-accounts mcp import-local --owner NAME (stdin JSON) machine-local overlay for
135
139
  a runner daemon; applied here, never synced
140
+ codex-accounts mcp auth-sync share native MCP files and refresh locks locally
141
+ Sign in with `codex mcp login NAME`; sign out with `codex mcp logout NAME`.
136
142
 
137
143
  ENV
138
144
  CODEX_ACCOUNTS_ROOT pool root, overriding ~/.codex-accounts — one isolated pool
@@ -142,10 +148,13 @@ ENV
142
148
  'none' = local-only, nothing is pushed anywhere
143
149
  CODEX_MULTIACC_SYNC_ROOT / _SYNC_REPO remote pool root / addon repo for it
144
150
  CODEX_ACCOUNT pin the shim to one account
151
+ CODEX_SPEED normal|fast|ultrafast for this launch; unavailable Ultrafast uses Fast
152
+ CODEX_SPEED_MODEL model hint for app-server launches that choose their model over RPC
145
153
  CODEX_SHIM_RETRY=0 disable the `codex exec` auto-retry
146
154
  CODEX_MULTIACC_DISABLE=1 bypass the shim entirely
147
155
  CODEX_MULTIACC_AUTO_RESET=0 disable automatic usage-reset redemption
148
156
  CODEX_MULTIACC_MCP=0 disable the MCP registry (no reconcile at seed/launch, no mirror)
157
+ CODEX_MULTIACC_MCP_OAUTH=0 stop preparing shared native MCP storage and locks
149
158
  CODEX_MULTIACC_MODEL_CATALOG=0 never refresh the codex pool's model-catalog.json
150
159
  (MULTIACC_MODEL_CATALOG=0: neither pool's)
151
160
  EOF
@@ -153,6 +162,12 @@ EOF
153
162
 
154
163
  require_manifest() { [ -f "$MANIFEST" ] || die "no manifest at $MANIFEST — run claude-multiacc install first"; }
155
164
 
165
+ cmd_speed() {
166
+ local native
167
+ native="$(find_real_codex "$_self" 2>/dev/null)" || native=""
168
+ "$PYBIN" "$LIB_DIR/codex_speed.py" --native-cli "$native" "$@"
169
+ }
170
+
156
171
  cmd_configure() {
157
172
  require_manifest
158
173
  local apply=0 check=0 arg rc
@@ -254,11 +269,11 @@ PYEOF
254
269
  # later contender legitimately re-took.
255
270
  login_lock_acquire() { # $1 = lock dir; nonzero when someone else holds it
256
271
  local L="$1"
257
- if ! mkdir "$L" 2>/dev/null; then
272
+ if ! atomic_mkdir "$L" 2>/dev/null; then
258
273
  [ $(( $(epoch_now) - $(file_mtime "$L") )) -gt 1800 ] || return 1
259
274
  mv "$L" "$L.stale.$$" 2>/dev/null || return 1
260
275
  rm -rf "$L.stale.$$" 2>/dev/null
261
- mkdir "$L" 2>/dev/null || return 1
276
+ atomic_mkdir "$L" 2>/dev/null || return 1
262
277
  fi
263
278
  : > "$L/owner.$$" 2>/dev/null || true
264
279
  return 0
@@ -1848,6 +1863,7 @@ PYEOF
1848
1863
  trap - EXIT
1849
1864
  # Every account's MCP servers, adopting what one holds that the registry does not know.
1850
1865
  mcp_registry_pass
1866
+ codex_mcp_oauth_ensure "$ACC_ROOT" "$PYBIN" "$LIB_DIR/codex_mcp_oauth.py"
1851
1867
  # This pool's model catalog, once its copy is an hour old (lib/common.sh).
1852
1868
  model_catalog_backstop
1853
1869
  # The refresher fails open per account; a NON-zero status means the pass itself
@@ -2264,10 +2280,14 @@ cmd_mcp() {
2264
2280
  local args=()
2265
2281
  case "$sub" in
2266
2282
  add|add-json|remove|list|apply|import-local) shift ;;
2267
- # Sharing MCP OAuth logins across accounts is a claude-pool feature (lib/mcp_oauth.py):
2268
- # Codex keeps an MCP server's OAuth state in its own per-account store.
2269
- auth-status|auth-sync|auth-forget)
2270
- die "mcp $sub is not supported for the codex pool — MCP OAuth logins are shared across the claude pool only (claude-accounts mcp $sub)" ;;
2283
+ auth-sync)
2284
+ shift
2285
+ local real
2286
+ real="$(find_real_codex "$_self")" || die "real codex binary not found"
2287
+ "$PYBIN" "$LIB_DIR/codex_mcp_oauth.py" ensure --root "$ACC_ROOT" --codex-bin "$real" "$@"
2288
+ return $? ;;
2289
+ auth-status|auth-forget)
2290
+ die "use codex mcp list for native auth status, or codex mcp logout NAME to clear shared MCP authentication" ;;
2271
2291
  "") usage; return 1 ;;
2272
2292
  *) die "unknown mcp subcommand: $sub (add, add-json, remove, list, apply, import-local)" ;;
2273
2293
  esac
@@ -2469,7 +2489,7 @@ PYEOF
2469
2489
  # distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
2470
2490
  # FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
2471
2491
  # the dispatcher below implements, and a test pins that both ways.
2472
- _KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync configure verify limits post-sync health self-update init-pool mcp model-catalog"
2492
+ _KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync configure verify limits post-sync health self-update init-pool mcp model-catalog speed"
2473
2493
  case " $_KNOWN_VERBS " in
2474
2494
  *" ${1:-help} "*)
2475
2495
  # ...but never past a literal `--`: from there on the words belong to an MCP
@@ -2502,6 +2522,7 @@ case "${1:-help}" in
2502
2522
  init-pool) shift; cmd_init_pool "$@" ;;
2503
2523
  mcp) shift; cmd_mcp "$@" ;;
2504
2524
  model-catalog) shift; cmd_model_catalog "$@" ;;
2525
+ speed) shift; cmd_speed "$@" ;;
2505
2526
  help|--help|-h) usage ;;
2506
2527
  *) usage; exit 1 ;;
2507
2528
  esac
@@ -260,8 +260,8 @@ What to expect, and how to read `auth-status`:
260
260
  re-authentication in ANY one account revokes the shared login at the server, so the pool
261
261
  retires it in every account (`login: none — retired … (cleared in acct-NN file)`); one
262
262
  sign-in in any account shares the new one. `mcp auth-forget NAME` is the same logout from
263
- the operator's side. `codex-accounts mcp auth-*` is not supported (Codex keeps MCP OAuth in
264
- its own store).
263
+ the operator's side. Codex uses native login/logout over shared local storage;
264
+ `codex-accounts mcp auth-sync` repairs it. See [Codex MCP authentication](CODEX_MCP_AUTH.md).
265
265
  - **Rolling it out**: the first share runs at install time (the limits agent starts on
266
266
  install), and a build older than this one reads a shared MCP-only file as a live login:
267
267
  every setup-token account would then say "Not logged in". Install it only where no older
@@ -164,7 +164,12 @@ shell: runs the shim again → token → marker on the old account → normal
164
164
  1. It consumes the token. A token is single-use and valid for 10 minutes.
165
165
  2. It writes the old account's marker with the pool's existing writers (see the tables
166
166
  below).
167
- 3. It `cd`s to the session's directory.
167
+ 3. It `cd`s to the session's directory. For Codex, this is the latest directory saved
168
+ by that session, including a change since its first turn. Starting or resuming it
169
+ from another shell directory therefore does not stop the automatic restart at
170
+ **Working directory · resume**. The selected account trusts this same directory
171
+ before startup. If the saved directory is unavailable, an existing initial session
172
+ directory or launch directory is used as a fallback.
168
173
  4. It runs **normal selection**, with one addition: the accounts this chain of
169
174
  relaunches left are skipped until their own reset. The selection policy is
170
175
  otherwise untouched.
@@ -220,7 +225,16 @@ The prompt is submitted automatically:
220
225
  - **To replace the whole text**, set `CLAUDE_MULTIACC_AUTORESUME_PROMPT` or
221
226
  `CODEX_MULTIACC_AUTORESUME_PROMPT`.
222
227
 
223
- ### Model and effort
228
+ ### Model, effort, and Codex speed
229
+
230
+ Codex relaunches also carry the most recently saved `service_tier` from the
231
+ thread's own settings or turn context. Normal, Fast, and Ultrafast remain the same
232
+ across account rotation; Ultrafast becomes Fast when the selected destination
233
+ account/model does not advertise it. Claude receives no speed flag. The shim
234
+ updates the named thread's tier through native `thread/resume` before launching
235
+ the CLI, because native resume restores a saved tier ahead of `-c` configuration.
236
+ See [Session speed](CODEX.md#session-speed) for capability discovery and the
237
+ `CODEX_SPEED` launch override.
224
238
 
225
239
  The watcher reads the model and effort from the session's own history when it decides to
226
240
  relaunch, so a `/model` or `/effort` switch made after the launch is honoured:
package/docs/CODEX.md CHANGED
@@ -18,8 +18,21 @@ then the 30-point weekly-headroom band with random spread, ≥90% any-bucket
18
18
  exclusion, peers rotate), same marker semantics (`.limited` cooldowns, `.expired` parks with
19
19
  credential/policy scoping and soft expiry), same fail-open guarantees, same sync
20
20
  safety guards. The `codex` shim engages the buffered auto-retry only for
21
- `codex exec` runs with finite stdin, exactly like `-p` on the claude side. Interactive
22
- `codex` in tmux gets [auto-resume](AUTORESUME.md) instead, like `claude`.
21
+ `codex exec` runs with file/device stdin and explicit `codex exec ... -` prompts from pipes.
22
+ The latter require EOF in the native CLI too; the shim saves and replays the exact prompt
23
+ on its one account retry. Unknown commands, interactive stdin and non-consuming live pipes
24
+ remain unbuffered. Explicit `CODEX_HOME` and `CODEX_ACCOUNT` pins still bypass rotation.
25
+ Cancelling a buffered run forwards TERM/INT/HUP to its active native process and reaps it
26
+ before removing the temporary prompt/output files.
27
+ Interactive `codex` in tmux gets [auto-resume](AUTORESUME.md) instead, like `claude`.
28
+
29
+ Automatic selection exports `CODEX_MULTIACC_SELECTED_HOME` equal to the serving
30
+ `CODEX_HOME`, including after a retry. Selecting an explicit `CODEX_ACCOUNT` clears the marker;
31
+ a home passthrough never creates it. Nested invocations still pass through.
32
+ An isolated job launcher may request fresh pool selection only when `CODEX_SHIM_ACTIVE=1`,
33
+ the marker exactly matches `CODEX_HOME`, no `CODEX_ACCOUNT` is set and
34
+ `CODEX_MULTIACC_DISABLE` is not `1`. Omit the inherited home/guard/marker only for that
35
+ child. Unmarked older sessions and overridden homes retain their existing pin behavior.
23
36
 
24
37
  Codex-specific notes:
25
38
 
@@ -79,6 +92,45 @@ Codex-specific notes:
79
92
  - **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
80
93
  addon's no-API-keys rule.
81
94
 
95
+ ## Session speed
96
+
97
+ Set `CODEX_SPEED=normal`, `fast`, or `ultrafast` for a Codex launch. Normal explicitly
98
+ uses the standard tier, even when an account's configuration defaults to Fast.
99
+ The setting also works with `CODEX_ACCOUNT` and `CODEX_HOME` pins. Explicit native
100
+ `-c service_tier=...` overrides take precedence. Claude has no corresponding option.
101
+
102
+ ```bash
103
+ CODEX_SPEED=fast codex
104
+ CODEX_SPEED=ultrafast codex exec -m gpt-6.1-sol 'Review the changed code'
105
+ codex-accounts speed --account-dir ~/.codex-accounts/acct-18 --model gpt-6.1-sol --speed ultrafast --json
106
+ ```
107
+
108
+ The shim reads **the selected account's** native model catalog, never an e-mail or
109
+ a plan-name guess. Ultrafast falls back to Fast when that account/model does not
110
+ advertise it. Every Ultrafast launch verifies the native account's `model/list`
111
+ with no inference, so even a copied cache cannot grant another account access;
112
+ an inconclusive read falls back to Fast.
113
+ Catalog probes use a temporary SQLite index, preserving the account's normal
114
+ session-index work. The inspection command above uses the same native check for
115
+ Ultrafast.
116
+
117
+ Quota retries resolve the speed again for the next account. Automatic quota
118
+ resumes carry the speed recorded in the session history. For those resumes and
119
+ explicit speed requests on a named session UUID, the shim applies the resolved
120
+ tier through native `thread/resume` before launching the resumed CLI: Codex
121
+ currently restores its saved tier ahead of process configuration. That
122
+ preparation sends no turn and preserves the conversation. A failed preparation
123
+ stops the launch instead of silently selecting another speed.
124
+
125
+ App-server callers may supply `CODEX_SPEED_MODEL` when their model is chosen over
126
+ RPC. Read the resolved `config.service_tier` with native `config/read` and pass it
127
+ as `serviceTier` on `thread/start`, `thread/resume`, and `turn/start`; otherwise
128
+ saved thread settings can override the process configuration.
129
+
130
+ The [official speed documentation](https://learn.chatgpt.com/docs/agent-configuration/speed)
131
+ describes Fast's higher usage and Ultrafast access for Pro $500 and eligible
132
+ Enterprise/Edu plans. Actual account/model capabilities remain authoritative.
133
+
82
134
  ## Spendable Codex credits
83
135
 
84
136
  The existing authenticated usage GET also supplies the account's spendable Codex
@@ -215,6 +267,10 @@ claude-multiacc codex status # same status command via the npm wrapper
215
267
 
216
268
  ## MCP servers for every account
217
269
 
270
+ OAuth logins also need shared storage; see
271
+ [Codex MCP authentication](CODEX_MCP_AUTH.md). `codex-accounts mcp auth-sync` repairs
272
+ local credential and refresh-lock sharing without touching account `auth.json` files.
273
+
218
274
  `codex-accounts mcp add|add-json|remove|list|apply|import-local` manages the pool's MCP
219
275
  registry (`~/.codex-accounts/mcp-servers.json`); `add`/`remove` default to `--provider both`,
220
276
  so the claude pool beside it gets the same change, and `mcp remove` retires the server in
@@ -0,0 +1,58 @@
1
+ # Codex MCP authentication across pooled accounts
2
+
3
+ The MCP registry shares server configuration. Codex stores OAuth logins separately:
4
+ the direct OS keyring is shared, but its fallback `.credentials.json` belongs to
5
+ `CODEX_HOME`. On 2026-10-23 MotionSites was configured in all 18 accounts, while its
6
+ login existed only in `acct-06`. The other 17 reported `not_logged_in`.
7
+
8
+ The addon prepares one machine-local native MCP credential file and one directory
9
+ of native refresh/store locks under `<pool>/.codex-mcp-oauth/`. Accounts hardlink
10
+ `.credentials.json` to that file and link `mcp-oauth-locks` to the shared locks.
11
+ Codex reads, refreshes, saves and logs out using its own implementation. The addon
12
+ never refreshes a token or reads the OS keyring. Account `auth.json` files remain
13
+ independent, and none of this MCP state is included in fleet sync.
14
+
15
+ ```bash
16
+ codex-accounts mcp auth-sync # repair storage sharing now
17
+ codex mcp login motionsites # authorize once on this machine
18
+ codex mcp list # native authentication status
19
+ codex mcp logout motionsites # clear the shared login
20
+ ```
21
+
22
+ Preparation runs before pooled launches, during seeding, and after limits passes.
23
+ It requires native Codex 0.163.0 or newer, whose storage and lock contract is checked
24
+ before preparation; older clients keep their existing stores.
25
+ A first login saved after preparation is picked up at the next launch or explicit
26
+ `auth-sync`. Restart an already running session to reload its MCP connections.
27
+ `CODEX_MULTIACC_MCP_OAUTH=0` stops preparation; it does not undo existing shared
28
+ storage. Each machine still needs its own authorization.
29
+
30
+ ## Native storage constraints
31
+
32
+ Codex 0.163.0 refuses final-component credential symlinks with `O_NOFOLLOW`, so a
33
+ regular hardlinked file is necessary. Its file writer updates that inode in place
34
+ under `mcp-oauth-locks/file-store.lock`. A tokenless reserved entry keeps the native
35
+ document nonempty after its last real login is removed: otherwise Codex unlinks
36
+ only the account's filename, leaving the other hardlinks with the old login.
37
+ The reserved entry is not a configured server and cannot authenticate a request.
38
+
39
+ The refresh lock must be shared too: native locks normally live under each
40
+ `CODEX_HOME`, even when the grant is in the globally shared direct OS keyring.
41
+ Sharing a rotating grant without its lock lets simultaneous sessions reuse an
42
+ obsolete refresh token. Preparation preserves existing lock inodes, defers busy
43
+ stores and refuses independent credential documents rather than choosing a grant.
44
+ Initial migration atomically exchanges native lock directories with their shared
45
+ links. It retains discarded lock files until preexisting client handles close;
46
+ an operation racing that first migration can make preparation wait for its native
47
+ lock timeout. Later launches use the existing shared paths without that migration.
48
+ Linux excludes an inaccessible process only after proving that it has exited with
49
+ no living threads; inaccessible live or uncertain handle state still defers migration.
50
+ Only manifest accounts are eligible, including the supported `acct-NN → ~/.codex`
51
+ adoption. Foreign directory symlinks, duplicate homes and unsupported scoped
52
+ credential entries are refused. Encrypted secrets storage is not merged.
53
+
54
+ Implementation: `lib/codex_mcp_oauth.py` and its supporting modules. Native contract:
55
+ [OAuth storage](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/rmcp-client/src/oauth.rs)
56
+ and [refresh locks](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/rmcp-client/src/oauth/refresh_lock.rs).
57
+ The supported store settings are documented in the
58
+ [OpenAI configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference).