@with-logic/elwood 0.2.1 → 0.2.2

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 (871) hide show
  1. package/CHANGELOG.md +565 -1
  2. package/CONTRIBUTING.md +3 -0
  3. package/README.md +4 -2
  4. package/dist/bridge/socket-release.d.ts +15 -0
  5. package/dist/bridge/socket-release.d.ts.map +1 -0
  6. package/dist/bridge/socket-release.js +22 -0
  7. package/dist/bridge/socket-release.js.map +1 -0
  8. package/dist/claude/composer/border.d.ts +12 -0
  9. package/dist/claude/composer/border.d.ts.map +1 -0
  10. package/dist/claude/composer/border.js +12 -0
  11. package/dist/claude/composer/border.js.map +1 -0
  12. package/dist/claude/composer/empty-input.d.ts +15 -0
  13. package/dist/claude/composer/empty-input.d.ts.map +1 -1
  14. package/dist/claude/composer/empty-input.js +30 -11
  15. package/dist/claude/composer/empty-input.js.map +1 -1
  16. package/dist/claude/composer/footer.d.ts +26 -2
  17. package/dist/claude/composer/footer.d.ts.map +1 -1
  18. package/dist/claude/composer/footer.js +29 -2
  19. package/dist/claude/composer/footer.js.map +1 -1
  20. package/dist/claude/composer/idle.d.ts +14 -0
  21. package/dist/claude/composer/idle.d.ts.map +1 -0
  22. package/dist/claude/composer/idle.js +17 -0
  23. package/dist/claude/composer/idle.js.map +1 -0
  24. package/dist/claude/composer/last.d.ts +2 -0
  25. package/dist/claude/composer/last.d.ts.map +1 -0
  26. package/dist/claude/composer/last.js +15 -0
  27. package/dist/claude/composer/last.js.map +1 -0
  28. package/dist/claude/composer/staged-input.d.ts.map +1 -1
  29. package/dist/claude/composer/staged-input.js +2 -1
  30. package/dist/claude/composer/staged-input.js.map +1 -1
  31. package/dist/claude/hooks/stop-failure.d.ts +15 -2
  32. package/dist/claude/hooks/stop-failure.d.ts.map +1 -1
  33. package/dist/claude/hooks/stop-failure.js +32 -9
  34. package/dist/claude/hooks/stop-failure.js.map +1 -1
  35. package/dist/claude/login/stages.d.ts.map +1 -1
  36. package/dist/claude/login/stages.js +2 -2
  37. package/dist/claude/login/stages.js.map +1 -1
  38. package/dist/claude/model-picker.d.ts +1 -1
  39. package/dist/claude/model-picker.d.ts.map +1 -1
  40. package/dist/claude/model-picker.js +27 -10
  41. package/dist/claude/model-picker.js.map +1 -1
  42. package/dist/claude/preflight.d.ts +1 -1
  43. package/dist/claude/preflight.d.ts.map +1 -1
  44. package/dist/claude/preflight.js +18 -12
  45. package/dist/claude/preflight.js.map +1 -1
  46. package/dist/claude/questions/navigation.d.ts.map +1 -1
  47. package/dist/claude/questions/navigation.js +1 -2
  48. package/dist/claude/questions/navigation.js.map +1 -1
  49. package/dist/claude/questions/screen.d.ts.map +1 -1
  50. package/dist/claude/questions/screen.js +10 -2
  51. package/dist/claude/questions/screen.js.map +1 -1
  52. package/dist/claude/screen-table.d.ts +8 -5
  53. package/dist/claude/screen-table.d.ts.map +1 -1
  54. package/dist/claude/screen-table.js +20 -12
  55. package/dist/claude/screen-table.js.map +1 -1
  56. package/dist/claude/session/bridge.d.ts +2 -1
  57. package/dist/claude/session/bridge.d.ts.map +1 -1
  58. package/dist/claude/session/bridge.js +3 -1
  59. package/dist/claude/session/bridge.js.map +1 -1
  60. package/dist/claude/session/build.d.ts.map +1 -1
  61. package/dist/claude/session/build.js +13 -11
  62. package/dist/claude/session/build.js.map +1 -1
  63. package/dist/claude/session/cleanup.d.ts +18 -0
  64. package/dist/claude/session/cleanup.d.ts.map +1 -0
  65. package/dist/claude/session/cleanup.js +10 -0
  66. package/dist/claude/session/cleanup.js.map +1 -0
  67. package/dist/claude/session/index.d.ts.map +1 -1
  68. package/dist/claude/session/index.js +27 -13
  69. package/dist/claude/session/index.js.map +1 -1
  70. package/dist/claude/session/instance.d.ts +7 -8
  71. package/dist/claude/session/instance.d.ts.map +1 -1
  72. package/dist/claude/session/instance.js +9 -13
  73. package/dist/claude/session/instance.js.map +1 -1
  74. package/dist/claude/session/resume.d.ts.map +1 -1
  75. package/dist/claude/session/resume.js +14 -5
  76. package/dist/claude/session/resume.js.map +1 -1
  77. package/dist/claude/session/transcript.js +2 -2
  78. package/dist/claude/session/transcript.js.map +1 -1
  79. package/dist/claude/simple.d.ts +1 -1
  80. package/dist/claude/simple.d.ts.map +1 -1
  81. package/dist/claude/simple.js +3 -3
  82. package/dist/claude/simple.js.map +1 -1
  83. package/dist/claude/startup-prompts.d.ts +2 -0
  84. package/dist/claude/startup-prompts.d.ts.map +1 -1
  85. package/dist/claude/startup-prompts.js +5 -1
  86. package/dist/claude/startup-prompts.js.map +1 -1
  87. package/dist/claude/transcript/cursor-io.d.ts +1 -1
  88. package/dist/claude/transcript/cursor-io.d.ts.map +1 -1
  89. package/dist/claude/transcript/cursor-io.js +1 -1
  90. package/dist/claude/transcript/cursor-io.js.map +1 -1
  91. package/dist/claude/transcript/cursor.d.ts +5 -3
  92. package/dist/claude/transcript/cursor.d.ts.map +1 -1
  93. package/dist/claude/transcript/cursor.js +18 -8
  94. package/dist/claude/transcript/cursor.js.map +1 -1
  95. package/dist/claude/transcript/drain-context.d.ts +4 -3
  96. package/dist/claude/transcript/drain-context.d.ts.map +1 -1
  97. package/dist/claude/transcript/drain-context.js.map +1 -1
  98. package/dist/claude/transcript/emit.d.ts +2 -1
  99. package/dist/claude/transcript/emit.d.ts.map +1 -1
  100. package/dist/claude/transcript/emit.js +1 -0
  101. package/dist/claude/transcript/emit.js.map +1 -1
  102. package/dist/claude/transcript/index.d.ts.map +1 -1
  103. package/dist/claude/transcript/index.js +2 -2
  104. package/dist/claude/transcript/index.js.map +1 -1
  105. package/dist/claude/transcript/warnings.d.ts +2 -1
  106. package/dist/claude/transcript/warnings.d.ts.map +1 -1
  107. package/dist/claude/transcript/warnings.js +3 -2
  108. package/dist/claude/transcript/warnings.js.map +1 -1
  109. package/dist/cli/config/effective.d.ts +2 -2
  110. package/dist/cli/config/effective.d.ts.map +1 -1
  111. package/dist/cli/config/effective.js +0 -2
  112. package/dist/cli/config/effective.js.map +1 -1
  113. package/dist/cli/interactive/spawn.d.ts +0 -2
  114. package/dist/cli/interactive/spawn.d.ts.map +1 -1
  115. package/dist/cli/interactive/spawn.js +27 -24
  116. package/dist/cli/interactive/spawn.js.map +1 -1
  117. package/dist/cli/lifecycle/index.d.ts.map +1 -1
  118. package/dist/cli/lifecycle/index.js +5 -3
  119. package/dist/cli/lifecycle/index.js.map +1 -1
  120. package/dist/cli/main-failure.d.ts.map +1 -1
  121. package/dist/cli/main-failure.js +5 -2
  122. package/dist/cli/main-failure.js.map +1 -1
  123. package/dist/cli/models/combined.d.ts.map +1 -1
  124. package/dist/cli/models/combined.js +3 -2
  125. package/dist/cli/models/combined.js.map +1 -1
  126. package/dist/cli/models/render.d.ts.map +1 -1
  127. package/dist/cli/models/render.js +2 -1
  128. package/dist/cli/models/render.js.map +1 -1
  129. package/dist/cli/output/cleanup-error.d.ts +22 -0
  130. package/dist/cli/output/cleanup-error.d.ts.map +1 -0
  131. package/dist/cli/output/cleanup-error.js +67 -0
  132. package/dist/cli/output/cleanup-error.js.map +1 -0
  133. package/dist/cli/output/types.d.ts +6 -2
  134. package/dist/cli/output/types.d.ts.map +1 -1
  135. package/dist/cli/request/agent-detect.d.ts +6 -2
  136. package/dist/cli/request/agent-detect.d.ts.map +1 -1
  137. package/dist/cli/request/agent-detect.js +14 -6
  138. package/dist/cli/request/agent-detect.js.map +1 -1
  139. package/dist/cli/request/finalize.d.ts +1 -1
  140. package/dist/cli/request/finalize.d.ts.map +1 -1
  141. package/dist/cli/request/finalize.js +4 -25
  142. package/dist/cli/request/finalize.js.map +1 -1
  143. package/dist/cli/request/index.d.ts.map +1 -1
  144. package/dist/cli/request/index.js +11 -5
  145. package/dist/cli/request/index.js.map +1 -1
  146. package/dist/cli/request/path-replacement.d.ts +27 -0
  147. package/dist/cli/request/path-replacement.d.ts.map +1 -0
  148. package/dist/cli/request/path-replacement.js +79 -0
  149. package/dist/cli/request/path-replacement.js.map +1 -0
  150. package/dist/cli/request/workspace.d.ts +8 -0
  151. package/dist/cli/request/workspace.d.ts.map +1 -0
  152. package/dist/cli/request/workspace.js +50 -0
  153. package/dist/cli/request/workspace.js.map +1 -0
  154. package/dist/cli/run/output.d.ts.map +1 -1
  155. package/dist/cli/run/output.js +2 -1
  156. package/dist/cli/run/output.js.map +1 -1
  157. package/dist/cli/run/subscribe.d.ts.map +1 -1
  158. package/dist/cli/run/subscribe.js +1 -1
  159. package/dist/cli/run/subscribe.js.map +1 -1
  160. package/dist/cli/session/index.d.ts +4 -4
  161. package/dist/cli/session/index.d.ts.map +1 -1
  162. package/dist/cli/session/index.js +5 -5
  163. package/dist/cli/session/index.js.map +1 -1
  164. package/dist/cli/session/launch.d.ts +2 -2
  165. package/dist/cli/session/launch.d.ts.map +1 -1
  166. package/dist/cli/session/launch.js +12 -3
  167. package/dist/cli/session/launch.js.map +1 -1
  168. package/dist/cli/session/turn-readers.d.ts +14 -0
  169. package/dist/cli/session/turn-readers.d.ts.map +1 -0
  170. package/dist/cli/session/turn-readers.js +13 -0
  171. package/dist/cli/session/turn-readers.js.map +1 -0
  172. package/dist/codex/config/default-model.d.ts +24 -0
  173. package/dist/codex/config/default-model.d.ts.map +1 -0
  174. package/dist/codex/config/default-model.js +133 -0
  175. package/dist/codex/config/default-model.js.map +1 -0
  176. package/dist/codex/config/home.d.ts +35 -0
  177. package/dist/codex/config/home.d.ts.map +1 -0
  178. package/dist/codex/config/home.js +122 -0
  179. package/dist/codex/config/home.js.map +1 -0
  180. package/dist/codex/config/lock.d.ts +17 -11
  181. package/dist/codex/config/lock.d.ts.map +1 -1
  182. package/dist/codex/config/lock.js +102 -11
  183. package/dist/codex/config/lock.js.map +1 -1
  184. package/dist/codex/config/read.d.ts +16 -0
  185. package/dist/codex/config/read.d.ts.map +1 -0
  186. package/dist/codex/config/read.js +75 -0
  187. package/dist/codex/config/read.js.map +1 -0
  188. package/dist/codex/config/restore.d.ts +17 -7
  189. package/dist/codex/config/restore.d.ts.map +1 -1
  190. package/dist/codex/config/restore.js +33 -20
  191. package/dist/codex/config/restore.js.map +1 -1
  192. package/dist/codex/config/transaction.d.ts +6 -4
  193. package/dist/codex/config/transaction.d.ts.map +1 -1
  194. package/dist/codex/config/transaction.js +4 -3
  195. package/dist/codex/config/transaction.js.map +1 -1
  196. package/dist/codex/images/clipboard-copies.d.ts +39 -0
  197. package/dist/codex/images/clipboard-copies.d.ts.map +1 -0
  198. package/dist/codex/images/clipboard-copies.js +91 -0
  199. package/dist/codex/images/clipboard-copies.js.map +1 -0
  200. package/dist/codex/model-picker.d.ts +1 -1
  201. package/dist/codex/model-picker.d.ts.map +1 -1
  202. package/dist/codex/model-picker.js +16 -6
  203. package/dist/codex/model-picker.js.map +1 -1
  204. package/dist/codex/preflight.d.ts +2 -2
  205. package/dist/codex/preflight.d.ts.map +1 -1
  206. package/dist/codex/preflight.js +28 -21
  207. package/dist/codex/preflight.js.map +1 -1
  208. package/dist/codex/pty.d.ts +25 -3
  209. package/dist/codex/pty.d.ts.map +1 -1
  210. package/dist/codex/pty.js +53 -7
  211. package/dist/codex/pty.js.map +1 -1
  212. package/dist/codex/questions/candidates.d.ts.map +1 -1
  213. package/dist/codex/questions/candidates.js +4 -5
  214. package/dist/codex/questions/candidates.js.map +1 -1
  215. package/dist/codex/questions/driver.js +2 -2
  216. package/dist/codex/questions/driver.js.map +1 -1
  217. package/dist/codex/questions/records.d.ts +1 -1
  218. package/dist/codex/questions/records.d.ts.map +1 -1
  219. package/dist/codex/questions/records.js +1 -3
  220. package/dist/codex/questions/records.js.map +1 -1
  221. package/dist/codex/screen/clearance.d.ts +6 -0
  222. package/dist/codex/screen/clearance.d.ts.map +1 -1
  223. package/dist/codex/screen/clearance.js +10 -0
  224. package/dist/codex/screen/clearance.js.map +1 -1
  225. package/dist/codex/screen-table.d.ts.map +1 -1
  226. package/dist/codex/screen-table.js +6 -2
  227. package/dist/codex/screen-table.js.map +1 -1
  228. package/dist/codex/session/bridge.d.ts +1 -0
  229. package/dist/codex/session/bridge.d.ts.map +1 -1
  230. package/dist/codex/session/bridge.js +3 -1
  231. package/dist/codex/session/bridge.js.map +1 -1
  232. package/dist/codex/session/build.d.ts.map +1 -1
  233. package/dist/codex/session/build.js +14 -12
  234. package/dist/codex/session/build.js.map +1 -1
  235. package/dist/codex/session/cleanup.d.ts +2 -2
  236. package/dist/codex/session/cleanup.d.ts.map +1 -1
  237. package/dist/codex/session/cleanup.js +8 -11
  238. package/dist/codex/session/cleanup.js.map +1 -1
  239. package/dist/codex/session/config-switch.d.ts +20 -0
  240. package/dist/codex/session/config-switch.d.ts.map +1 -0
  241. package/dist/codex/session/config-switch.js +42 -0
  242. package/dist/codex/session/config-switch.js.map +1 -0
  243. package/dist/codex/session/hooks.d.ts.map +1 -1
  244. package/dist/codex/session/hooks.js +7 -0
  245. package/dist/codex/session/hooks.js.map +1 -1
  246. package/dist/codex/session/index.d.ts.map +1 -1
  247. package/dist/codex/session/index.js +34 -16
  248. package/dist/codex/session/index.js.map +1 -1
  249. package/dist/codex/session/instance.d.ts +10 -12
  250. package/dist/codex/session/instance.d.ts.map +1 -1
  251. package/dist/codex/session/instance.js +42 -37
  252. package/dist/codex/session/instance.js.map +1 -1
  253. package/dist/codex/session/readiness.d.ts +11 -0
  254. package/dist/codex/session/readiness.d.ts.map +1 -0
  255. package/dist/codex/session/readiness.js +22 -0
  256. package/dist/codex/session/readiness.js.map +1 -0
  257. package/dist/codex/session/resume.d.ts.map +1 -1
  258. package/dist/codex/session/resume.js +9 -4
  259. package/dist/codex/session/resume.js.map +1 -1
  260. package/dist/codex/session/transcript.d.ts.map +1 -1
  261. package/dist/codex/session/transcript.js +12 -1
  262. package/dist/codex/session/transcript.js.map +1 -1
  263. package/dist/codex/session/turn-commit.d.ts +58 -0
  264. package/dist/codex/session/turn-commit.d.ts.map +1 -0
  265. package/dist/codex/session/turn-commit.js +97 -0
  266. package/dist/codex/session/turn-commit.js.map +1 -0
  267. package/dist/codex/session/warnings.d.ts +13 -6
  268. package/dist/codex/session/warnings.d.ts.map +1 -1
  269. package/dist/codex/session/warnings.js +36 -9
  270. package/dist/codex/session/warnings.js.map +1 -1
  271. package/dist/codex/startup-prompts.d.ts +3 -0
  272. package/dist/codex/startup-prompts.d.ts.map +1 -1
  273. package/dist/codex/startup-prompts.js +20 -13
  274. package/dist/codex/startup-prompts.js.map +1 -1
  275. package/dist/codex/startup-warnings.d.ts +2 -0
  276. package/dist/codex/startup-warnings.d.ts.map +1 -1
  277. package/dist/codex/startup-warnings.js +6 -0
  278. package/dist/codex/startup-warnings.js.map +1 -1
  279. package/dist/codex/transcript/payload.d.ts +8 -0
  280. package/dist/codex/transcript/payload.d.ts.map +1 -0
  281. package/dist/codex/transcript/payload.js +11 -0
  282. package/dist/codex/transcript/payload.js.map +1 -0
  283. package/dist/codex/transcript/rejection.d.ts +15 -0
  284. package/dist/codex/transcript/rejection.d.ts.map +1 -0
  285. package/dist/codex/transcript/rejection.js +44 -0
  286. package/dist/codex/transcript/rejection.js.map +1 -0
  287. package/dist/codex/transcript/turn-end.d.ts +8 -0
  288. package/dist/codex/transcript/turn-end.d.ts.map +1 -0
  289. package/dist/codex/transcript/turn-end.js +17 -0
  290. package/dist/codex/transcript/turn-end.js.map +1 -0
  291. package/dist/codex/transcript/watcher.d.ts +5 -2
  292. package/dist/codex/transcript/watcher.d.ts.map +1 -1
  293. package/dist/codex/transcript/watcher.js +11 -6
  294. package/dist/codex/transcript/watcher.js.map +1 -1
  295. package/dist/codex/trust-retry.d.ts +12 -0
  296. package/dist/codex/trust-retry.d.ts.map +1 -0
  297. package/dist/codex/trust-retry.js +12 -0
  298. package/dist/codex/trust-retry.js.map +1 -0
  299. package/dist/codex/update/skip-attempt.d.ts +2 -0
  300. package/dist/codex/update/skip-attempt.d.ts.map +1 -1
  301. package/dist/codex/update/skip-attempt.js +4 -0
  302. package/dist/codex/update/skip-attempt.js.map +1 -1
  303. package/dist/codex/update/tracker.d.ts +2 -0
  304. package/dist/codex/update/tracker.d.ts.map +1 -1
  305. package/dist/codex/update/tracker.js +4 -0
  306. package/dist/codex/update/tracker.js.map +1 -1
  307. package/dist/core/delay.d.ts +3 -2
  308. package/dist/core/delay.d.ts.map +1 -1
  309. package/dist/core/delay.js +3 -2
  310. package/dist/core/delay.js.map +1 -1
  311. package/dist/core/errors.d.ts +33 -4
  312. package/dist/core/errors.d.ts.map +1 -1
  313. package/dist/core/errors.js +51 -7
  314. package/dist/core/errors.js.map +1 -1
  315. package/dist/core/images/materialize.d.ts +13 -4
  316. package/dist/core/images/materialize.d.ts.map +1 -1
  317. package/dist/core/images/materialize.js +48 -11
  318. package/dist/core/images/materialize.js.map +1 -1
  319. package/dist/core/input/abort.d.ts +15 -0
  320. package/dist/core/input/abort.d.ts.map +1 -1
  321. package/dist/core/input/abort.js +18 -2
  322. package/dist/core/input/abort.js.map +1 -1
  323. package/dist/core/input/constants.d.ts +4 -1
  324. package/dist/core/input/constants.d.ts.map +1 -1
  325. package/dist/core/input/constants.js +4 -1
  326. package/dist/core/input/constants.js.map +1 -1
  327. package/dist/core/input/index.d.ts +1 -1
  328. package/dist/core/input/index.d.ts.map +1 -1
  329. package/dist/core/input/index.js +1 -1
  330. package/dist/core/input/index.js.map +1 -1
  331. package/dist/core/models/clearance.d.ts +38 -0
  332. package/dist/core/models/clearance.d.ts.map +1 -0
  333. package/dist/core/models/clearance.js +38 -0
  334. package/dist/core/models/clearance.js.map +1 -0
  335. package/dist/core/models/close-wait.d.ts +17 -0
  336. package/dist/core/models/close-wait.d.ts.map +1 -0
  337. package/dist/core/models/close-wait.js +44 -0
  338. package/dist/core/models/close-wait.js.map +1 -0
  339. package/dist/core/models/dialog-region.d.ts +18 -3
  340. package/dist/core/models/dialog-region.d.ts.map +1 -1
  341. package/dist/core/models/dialog-region.js +29 -40
  342. package/dist/core/models/dialog-region.js.map +1 -1
  343. package/dist/core/models/input.d.ts +10 -1
  344. package/dist/core/models/input.d.ts.map +1 -1
  345. package/dist/core/models/input.js +15 -2
  346. package/dist/core/models/input.js.map +1 -1
  347. package/dist/core/models/intervention.d.ts +2 -2
  348. package/dist/core/models/intervention.d.ts.map +1 -1
  349. package/dist/core/models/intervention.js +6 -3
  350. package/dist/core/models/intervention.js.map +1 -1
  351. package/dist/core/models/own-picker.d.ts +21 -0
  352. package/dist/core/models/own-picker.d.ts.map +1 -0
  353. package/dist/core/models/own-picker.js +21 -0
  354. package/dist/core/models/own-picker.js.map +1 -0
  355. package/dist/core/models/picker.d.ts +34 -4
  356. package/dist/core/models/picker.d.ts.map +1 -1
  357. package/dist/core/models/picker.js +50 -44
  358. package/dist/core/models/picker.js.map +1 -1
  359. package/dist/core/models/row-block.d.ts +66 -0
  360. package/dist/core/models/row-block.d.ts.map +1 -0
  361. package/dist/core/models/row-block.js +87 -0
  362. package/dist/core/models/row-block.js.map +1 -0
  363. package/dist/core/models/rows.d.ts +32 -3
  364. package/dist/core/models/rows.d.ts.map +1 -1
  365. package/dist/core/models/rows.js +48 -20
  366. package/dist/core/models/rows.js.map +1 -1
  367. package/dist/core/models/tui-screen.d.ts +6 -1
  368. package/dist/core/models/tui-screen.d.ts.map +1 -1
  369. package/dist/core/models/tui-screen.js +25 -10
  370. package/dist/core/models/tui-screen.js.map +1 -1
  371. package/dist/core/models/window.d.ts +49 -0
  372. package/dist/core/models/window.d.ts.map +1 -0
  373. package/dist/core/models/window.js +105 -0
  374. package/dist/core/models/window.js.map +1 -0
  375. package/dist/core/path-trust.d.ts +11 -0
  376. package/dist/core/path-trust.d.ts.map +1 -0
  377. package/dist/core/path-trust.js +21 -0
  378. package/dist/core/path-trust.js.map +1 -0
  379. package/dist/core/screen-facts.d.ts +3 -1
  380. package/dist/core/screen-facts.d.ts.map +1 -1
  381. package/dist/core/screen-facts.js +1 -0
  382. package/dist/core/screen-facts.js.map +1 -1
  383. package/dist/core/simple/boundary-signal.d.ts +41 -6
  384. package/dist/core/simple/boundary-signal.d.ts.map +1 -1
  385. package/dist/core/simple/boundary-signal.js +43 -3
  386. package/dist/core/simple/boundary-signal.js.map +1 -1
  387. package/dist/core/simple/close.d.ts +2 -1
  388. package/dist/core/simple/close.d.ts.map +1 -1
  389. package/dist/core/simple/close.js +10 -2
  390. package/dist/core/simple/close.js.map +1 -1
  391. package/dist/core/simple/turn-boundary.d.ts +2 -0
  392. package/dist/core/simple/turn-boundary.d.ts.map +1 -1
  393. package/dist/core/simple/turn-boundary.js +1 -1
  394. package/dist/core/simple/turn-boundary.js.map +1 -1
  395. package/dist/core/simple/turn-types.d.ts +9 -3
  396. package/dist/core/simple/turn-types.d.ts.map +1 -1
  397. package/dist/core/simple/turn-types.js +10 -2
  398. package/dist/core/simple/turn-types.js.map +1 -1
  399. package/dist/core/simple/turn.d.ts +2 -0
  400. package/dist/core/simple/turn.d.ts.map +1 -1
  401. package/dist/core/simple/turn.js +11 -5
  402. package/dist/core/simple/turn.js.map +1 -1
  403. package/dist/core/terminal-options.d.ts +17 -1
  404. package/dist/core/terminal-options.d.ts.map +1 -1
  405. package/dist/core/terminal-options.js +20 -8
  406. package/dist/core/terminal-options.js.map +1 -1
  407. package/dist/core/transcript/cursor-io.d.ts +8 -0
  408. package/dist/core/transcript/cursor-io.d.ts.map +1 -1
  409. package/dist/core/transcript/cursor-io.js +11 -5
  410. package/dist/core/transcript/cursor-io.js.map +1 -1
  411. package/dist/core/transcript/cursor.d.ts +28 -4
  412. package/dist/core/transcript/cursor.d.ts.map +1 -1
  413. package/dist/core/transcript/cursor.js +53 -7
  414. package/dist/core/transcript/cursor.js.map +1 -1
  415. package/dist/core/transcript/drain.d.ts +43 -0
  416. package/dist/core/transcript/drain.d.ts.map +1 -0
  417. package/dist/core/transcript/drain.js +67 -0
  418. package/dist/core/transcript/drain.js.map +1 -0
  419. package/dist/core/trust/abortable.d.ts +12 -0
  420. package/dist/core/trust/abortable.d.ts.map +1 -0
  421. package/dist/core/trust/abortable.js +48 -0
  422. package/dist/core/trust/abortable.js.map +1 -0
  423. package/dist/core/trust/allowlist.d.ts +2 -0
  424. package/dist/core/trust/allowlist.d.ts.map +1 -1
  425. package/dist/core/trust/allowlist.js +4 -1
  426. package/dist/core/trust/allowlist.js.map +1 -1
  427. package/dist/core/trust/blocking.d.ts +11 -0
  428. package/dist/core/trust/blocking.d.ts.map +1 -1
  429. package/dist/core/trust/blocking.js +5 -4
  430. package/dist/core/trust/blocking.js.map +1 -1
  431. package/dist/core/trust/copy.d.ts.map +1 -1
  432. package/dist/core/trust/copy.js +7 -1
  433. package/dist/core/trust/copy.js.map +1 -1
  434. package/dist/core/trust/deadline.d.ts +24 -0
  435. package/dist/core/trust/deadline.d.ts.map +1 -0
  436. package/dist/core/trust/deadline.js +56 -0
  437. package/dist/core/trust/deadline.js.map +1 -0
  438. package/dist/core/trust/dialog.d.ts +11 -2
  439. package/dist/core/trust/dialog.d.ts.map +1 -1
  440. package/dist/core/trust/dialog.js +12 -3
  441. package/dist/core/trust/dialog.js.map +1 -1
  442. package/dist/core/trust/options.d.ts +5 -1
  443. package/dist/core/trust/options.d.ts.map +1 -1
  444. package/dist/core/trust/options.js +8 -2
  445. package/dist/core/trust/options.js.map +1 -1
  446. package/dist/core/trust/prompts.d.ts +3 -0
  447. package/dist/core/trust/prompts.d.ts.map +1 -1
  448. package/dist/core/trust/prompts.js +2 -1
  449. package/dist/core/trust/prompts.js.map +1 -1
  450. package/dist/core/trust/responder.d.ts +10 -5
  451. package/dist/core/trust/responder.d.ts.map +1 -1
  452. package/dist/core/trust/responder.js +37 -33
  453. package/dist/core/trust/responder.js.map +1 -1
  454. package/dist/core/trust/step.d.ts +67 -0
  455. package/dist/core/trust/step.d.ts.map +1 -0
  456. package/dist/core/trust/step.js +45 -0
  457. package/dist/core/trust/step.js.map +1 -0
  458. package/dist/core/trust/types.d.ts +21 -1
  459. package/dist/core/trust/types.d.ts.map +1 -1
  460. package/dist/core/trust/types.js +25 -1
  461. package/dist/core/trust/types.js.map +1 -1
  462. package/dist/core/trust/view.d.ts +2 -0
  463. package/dist/core/trust/view.d.ts.map +1 -1
  464. package/dist/core/trust/view.js +3 -0
  465. package/dist/core/trust/view.js.map +1 -1
  466. package/dist/core/trust/write.d.ts +31 -6
  467. package/dist/core/trust/write.d.ts.map +1 -1
  468. package/dist/core/trust/write.js +95 -80
  469. package/dist/core/trust/write.js.map +1 -1
  470. package/dist/core/turn-state.d.ts +18 -4
  471. package/dist/core/turn-state.d.ts.map +1 -1
  472. package/dist/core/turn-state.js +47 -25
  473. package/dist/core/turn-state.js.map +1 -1
  474. package/dist/core/warnings/images.d.ts +22 -0
  475. package/dist/core/warnings/images.d.ts.map +1 -0
  476. package/dist/core/warnings/images.js +3 -0
  477. package/dist/core/warnings/images.js.map +1 -0
  478. package/dist/core/warnings/index.d.ts +3 -2
  479. package/dist/core/warnings/index.d.ts.map +1 -1
  480. package/dist/core/warnings/lifecycle.d.ts +35 -4
  481. package/dist/core/warnings/lifecycle.d.ts.map +1 -1
  482. package/dist/core/warnings/lifecycle.js +1 -1
  483. package/dist/core/warnings/update.d.ts +8 -4
  484. package/dist/core/warnings/update.d.ts.map +1 -1
  485. package/dist/core/warnings/update.js +21 -6
  486. package/dist/core/warnings/update.js.map +1 -1
  487. package/dist/pty/exit-latch.d.ts +28 -0
  488. package/dist/pty/exit-latch.d.ts.map +1 -0
  489. package/dist/pty/exit-latch.js +50 -0
  490. package/dist/pty/exit-latch.js.map +1 -0
  491. package/dist/runtime/exit-status.d.ts +7 -0
  492. package/dist/runtime/exit-status.d.ts.map +1 -0
  493. package/dist/runtime/exit-status.js +13 -0
  494. package/dist/runtime/exit-status.js.map +1 -0
  495. package/dist/runtime/home-guard/index.d.ts +45 -0
  496. package/dist/runtime/home-guard/index.d.ts.map +1 -0
  497. package/dist/runtime/home-guard/index.js +69 -0
  498. package/dist/runtime/home-guard/index.js.map +1 -0
  499. package/dist/runtime/loop-restore.d.ts +9 -9
  500. package/dist/runtime/loop-restore.d.ts.map +1 -1
  501. package/dist/runtime/loop-restore.js +10 -10
  502. package/dist/runtime/loop-restore.js.map +1 -1
  503. package/dist/runtime/probe/capped-buffer.d.ts +16 -0
  504. package/dist/runtime/probe/capped-buffer.d.ts.map +1 -0
  505. package/dist/runtime/probe/capped-buffer.js +38 -0
  506. package/dist/runtime/probe/capped-buffer.js.map +1 -0
  507. package/dist/runtime/probe/capture.d.ts +55 -0
  508. package/dist/runtime/probe/capture.d.ts.map +1 -0
  509. package/dist/runtime/probe/capture.js +71 -0
  510. package/dist/runtime/probe/capture.js.map +1 -0
  511. package/dist/runtime/{probe-cleanup.d.ts → probe/cleanup.d.ts} +2 -2
  512. package/dist/runtime/probe/cleanup.d.ts.map +1 -0
  513. package/dist/runtime/{probe-cleanup.js → probe/cleanup.js} +3 -3
  514. package/dist/runtime/probe/cleanup.js.map +1 -0
  515. package/dist/runtime/probe/directory-key.d.ts +26 -0
  516. package/dist/runtime/probe/directory-key.d.ts.map +1 -0
  517. package/dist/runtime/probe/directory-key.js +46 -0
  518. package/dist/runtime/probe/directory-key.js.map +1 -0
  519. package/dist/runtime/probe/index.d.ts +25 -0
  520. package/dist/runtime/probe/index.d.ts.map +1 -0
  521. package/dist/runtime/probe/index.js +153 -0
  522. package/dist/runtime/probe/index.js.map +1 -0
  523. package/dist/runtime/probe/lookup.d.ts +12 -0
  524. package/dist/runtime/probe/lookup.d.ts.map +1 -0
  525. package/dist/runtime/probe/lookup.js +29 -0
  526. package/dist/runtime/probe/lookup.js.map +1 -0
  527. package/dist/runtime/probe/named-pipes.d.ts +35 -0
  528. package/dist/runtime/probe/named-pipes.d.ts.map +1 -0
  529. package/dist/runtime/probe/named-pipes.js +167 -0
  530. package/dist/runtime/probe/named-pipes.js.map +1 -0
  531. package/dist/runtime/probe/private-directory.d.ts +14 -0
  532. package/dist/runtime/probe/private-directory.d.ts.map +1 -0
  533. package/dist/runtime/probe/private-directory.js +50 -0
  534. package/dist/runtime/probe/private-directory.js.map +1 -0
  535. package/dist/runtime/probe/results.d.ts +14 -0
  536. package/dist/runtime/probe/results.d.ts.map +1 -0
  537. package/dist/runtime/probe/results.js +38 -0
  538. package/dist/runtime/probe/results.js.map +1 -0
  539. package/dist/runtime/probe/slots.d.ts +17 -0
  540. package/dist/runtime/probe/slots.d.ts.map +1 -0
  541. package/dist/runtime/probe/slots.js +35 -0
  542. package/dist/runtime/probe/slots.js.map +1 -0
  543. package/dist/runtime/probe/start.d.ts +64 -0
  544. package/dist/runtime/probe/start.d.ts.map +1 -0
  545. package/dist/runtime/probe/start.js +80 -0
  546. package/dist/runtime/probe/start.js.map +1 -0
  547. package/dist/runtime/probe-cache.d.ts +19 -0
  548. package/dist/runtime/probe-cache.d.ts.map +1 -0
  549. package/dist/runtime/probe-cache.js +56 -0
  550. package/dist/runtime/probe-cache.js.map +1 -0
  551. package/dist/runtime/readiness/initial-ready.d.ts +31 -5
  552. package/dist/runtime/readiness/initial-ready.d.ts.map +1 -1
  553. package/dist/runtime/readiness/initial-ready.js +64 -18
  554. package/dist/runtime/readiness/initial-ready.js.map +1 -1
  555. package/dist/runtime/seams.d.ts +14 -2
  556. package/dist/runtime/seams.d.ts.map +1 -1
  557. package/dist/runtime/seams.js +2 -2
  558. package/dist/runtime/seams.js.map +1 -1
  559. package/dist/runtime/session/base.d.ts.map +1 -1
  560. package/dist/runtime/session/base.js +6 -5
  561. package/dist/runtime/session/base.js.map +1 -1
  562. package/dist/runtime/session/commands.d.ts +5 -3
  563. package/dist/runtime/session/commands.d.ts.map +1 -1
  564. package/dist/runtime/session/commands.js +5 -2
  565. package/dist/runtime/session/commands.js.map +1 -1
  566. package/dist/runtime/session/frames.d.ts +12 -2
  567. package/dist/runtime/session/frames.d.ts.map +1 -1
  568. package/dist/runtime/session/frames.js +10 -3
  569. package/dist/runtime/session/frames.js.map +1 -1
  570. package/dist/runtime/session/image-attach.js +7 -2
  571. package/dist/runtime/session/image-attach.js.map +1 -1
  572. package/dist/runtime/session/launch-directory.d.ts +33 -0
  573. package/dist/runtime/session/launch-directory.d.ts.map +1 -0
  574. package/dist/runtime/session/launch-directory.js +84 -0
  575. package/dist/runtime/session/launch-directory.js.map +1 -0
  576. package/dist/runtime/session/lifecycle.d.ts +3 -3
  577. package/dist/runtime/session/lifecycle.d.ts.map +1 -1
  578. package/dist/runtime/session/lifecycle.js +1 -5
  579. package/dist/runtime/session/lifecycle.js.map +1 -1
  580. package/dist/runtime/session/loops.d.ts +1 -1
  581. package/dist/runtime/session/loops.d.ts.map +1 -1
  582. package/dist/runtime/session/loops.js +6 -3
  583. package/dist/runtime/session/loops.js.map +1 -1
  584. package/dist/runtime/session/picker-cleanup.d.ts +6 -21
  585. package/dist/runtime/session/picker-cleanup.d.ts.map +1 -1
  586. package/dist/runtime/session/picker-cleanup.js +19 -31
  587. package/dist/runtime/session/picker-cleanup.js.map +1 -1
  588. package/dist/runtime/session/picker-hold.d.ts +24 -0
  589. package/dist/runtime/session/picker-hold.d.ts.map +1 -0
  590. package/dist/runtime/session/picker-hold.js +42 -0
  591. package/dist/runtime/session/picker-hold.js.map +1 -0
  592. package/dist/runtime/session/picker.d.ts +13 -11
  593. package/dist/runtime/session/picker.d.ts.map +1 -1
  594. package/dist/runtime/session/picker.js +46 -24
  595. package/dist/runtime/session/picker.js.map +1 -1
  596. package/dist/runtime/session/prepare-state.d.ts +6 -0
  597. package/dist/runtime/session/prepare-state.d.ts.map +1 -0
  598. package/dist/runtime/session/prepare-state.js +14 -0
  599. package/dist/runtime/session/prepare-state.js.map +1 -0
  600. package/dist/runtime/session/questions.d.ts +2 -2
  601. package/dist/runtime/session/questions.d.ts.map +1 -1
  602. package/dist/runtime/session/questions.js.map +1 -1
  603. package/dist/runtime/session/readiness.d.ts +14 -5
  604. package/dist/runtime/session/readiness.d.ts.map +1 -1
  605. package/dist/runtime/session/readiness.js +22 -6
  606. package/dist/runtime/session/readiness.js.map +1 -1
  607. package/dist/runtime/session/reap.d.ts +11 -7
  608. package/dist/runtime/session/reap.d.ts.map +1 -1
  609. package/dist/runtime/session/reap.js +16 -10
  610. package/dist/runtime/session/reap.js.map +1 -1
  611. package/dist/runtime/session/runtime-cleanup.d.ts +18 -0
  612. package/dist/runtime/session/runtime-cleanup.d.ts.map +1 -0
  613. package/dist/runtime/session/runtime-cleanup.js +18 -0
  614. package/dist/runtime/session/runtime-cleanup.js.map +1 -0
  615. package/dist/runtime/session/session-input.d.ts +32 -0
  616. package/dist/runtime/session/session-input.d.ts.map +1 -0
  617. package/dist/runtime/session/{picker-input.js → session-input.js} +35 -7
  618. package/dist/runtime/session/session-input.js.map +1 -0
  619. package/dist/runtime/session/shutdown-binding.d.ts +12 -1
  620. package/dist/runtime/session/shutdown-binding.d.ts.map +1 -1
  621. package/dist/runtime/session/shutdown-binding.js +33 -1
  622. package/dist/runtime/session/shutdown-binding.js.map +1 -1
  623. package/dist/runtime/session/shutdown-cleanup.d.ts +17 -0
  624. package/dist/runtime/session/shutdown-cleanup.d.ts.map +1 -0
  625. package/dist/runtime/session/shutdown-cleanup.js +49 -0
  626. package/dist/runtime/session/shutdown-cleanup.js.map +1 -0
  627. package/dist/runtime/session/shutdown.d.ts +3 -2
  628. package/dist/runtime/session/shutdown.d.ts.map +1 -1
  629. package/dist/runtime/session/shutdown.js +13 -24
  630. package/dist/runtime/session/shutdown.js.map +1 -1
  631. package/dist/runtime/shell-quote.d.ts +8 -0
  632. package/dist/runtime/shell-quote.d.ts.map +1 -0
  633. package/dist/runtime/shell-quote.js +10 -0
  634. package/dist/runtime/shell-quote.js.map +1 -0
  635. package/dist/runtime/shell.d.ts +36 -12
  636. package/dist/runtime/shell.d.ts.map +1 -1
  637. package/dist/runtime/shell.js +90 -14
  638. package/dist/runtime/shell.js.map +1 -1
  639. package/dist/runtime/shutdown/coalesce.d.ts +15 -0
  640. package/dist/runtime/shutdown/coalesce.d.ts.map +1 -0
  641. package/dist/runtime/shutdown/coalesce.js +42 -0
  642. package/dist/runtime/shutdown/coalesce.js.map +1 -0
  643. package/dist/runtime/shutdown/detached-sweep/failures.d.ts +24 -0
  644. package/dist/runtime/shutdown/detached-sweep/failures.d.ts.map +1 -0
  645. package/dist/runtime/shutdown/detached-sweep/failures.js +32 -0
  646. package/dist/runtime/shutdown/detached-sweep/failures.js.map +1 -0
  647. package/dist/runtime/shutdown/detached-sweep/index.d.ts +55 -0
  648. package/dist/runtime/shutdown/detached-sweep/index.d.ts.map +1 -0
  649. package/dist/runtime/shutdown/detached-sweep/index.js +129 -0
  650. package/dist/runtime/shutdown/detached-sweep/index.js.map +1 -0
  651. package/dist/runtime/shutdown/detached-sweep/kill.d.ts +37 -0
  652. package/dist/runtime/shutdown/detached-sweep/kill.d.ts.map +1 -0
  653. package/dist/runtime/shutdown/detached-sweep/kill.js +87 -0
  654. package/dist/runtime/shutdown/detached-sweep/kill.js.map +1 -0
  655. package/dist/runtime/shutdown/detached-sweep/record.d.ts +67 -0
  656. package/dist/runtime/shutdown/detached-sweep/record.d.ts.map +1 -0
  657. package/dist/runtime/shutdown/detached-sweep/record.js +111 -0
  658. package/dist/runtime/shutdown/detached-sweep/record.js.map +1 -0
  659. package/dist/runtime/shutdown/detached-sweep/rounds.d.ts +78 -0
  660. package/dist/runtime/shutdown/detached-sweep/rounds.d.ts.map +1 -0
  661. package/dist/runtime/shutdown/detached-sweep/rounds.js +116 -0
  662. package/dist/runtime/shutdown/detached-sweep/rounds.js.map +1 -0
  663. package/dist/runtime/shutdown/detached-sweep/survivors.d.ts +40 -0
  664. package/dist/runtime/shutdown/detached-sweep/survivors.d.ts.map +1 -0
  665. package/dist/runtime/shutdown/detached-sweep/survivors.js +112 -0
  666. package/dist/runtime/shutdown/detached-sweep/survivors.js.map +1 -0
  667. package/dist/runtime/shutdown/leader-group.d.ts +29 -0
  668. package/dist/runtime/shutdown/leader-group.d.ts.map +1 -0
  669. package/dist/runtime/shutdown/leader-group.js +35 -0
  670. package/dist/runtime/shutdown/leader-group.js.map +1 -0
  671. package/dist/runtime/shutdown/native-read-admission.d.ts +13 -0
  672. package/dist/runtime/shutdown/native-read-admission.d.ts.map +1 -0
  673. package/dist/runtime/shutdown/native-read-admission.js +64 -0
  674. package/dist/runtime/shutdown/native-read-admission.js.map +1 -0
  675. package/dist/runtime/shutdown/process-table.d.ts +80 -0
  676. package/dist/runtime/shutdown/process-table.d.ts.map +1 -0
  677. package/dist/runtime/shutdown/process-table.js +147 -0
  678. package/dist/runtime/shutdown/process-table.js.map +1 -0
  679. package/dist/runtime/shutdown/process-tree-ops.d.ts +39 -0
  680. package/dist/runtime/shutdown/process-tree-ops.d.ts.map +1 -0
  681. package/dist/runtime/shutdown/process-tree-ops.js +73 -0
  682. package/dist/runtime/shutdown/process-tree-ops.js.map +1 -0
  683. package/dist/runtime/shutdown/read-timeout.d.ts +4 -0
  684. package/dist/runtime/shutdown/read-timeout.d.ts.map +1 -0
  685. package/dist/runtime/shutdown/read-timeout.js +6 -0
  686. package/dist/runtime/shutdown/read-timeout.js.map +1 -0
  687. package/dist/runtime/shutdown/reap-queue.d.ts +10 -0
  688. package/dist/runtime/shutdown/reap-queue.d.ts.map +1 -0
  689. package/dist/runtime/shutdown/reap-queue.js +29 -0
  690. package/dist/runtime/shutdown/reap-queue.js.map +1 -0
  691. package/dist/runtime/shutdown/reap-tree.d.ts +42 -32
  692. package/dist/runtime/shutdown/reap-tree.d.ts.map +1 -1
  693. package/dist/runtime/shutdown/reap-tree.js +78 -71
  694. package/dist/runtime/shutdown/reap-tree.js.map +1 -1
  695. package/dist/runtime/shutdown/shared-daemon.d.ts +59 -0
  696. package/dist/runtime/shutdown/shared-daemon.d.ts.map +1 -0
  697. package/dist/runtime/shutdown/shared-daemon.js +139 -0
  698. package/dist/runtime/shutdown/shared-daemon.js.map +1 -0
  699. package/dist/runtime/shutdown/teardown.d.ts +15 -0
  700. package/dist/runtime/shutdown/teardown.d.ts.map +1 -1
  701. package/dist/runtime/shutdown/teardown.js +19 -6
  702. package/dist/runtime/shutdown/teardown.js.map +1 -1
  703. package/dist/runtime/shutdown/terminate.d.ts +21 -1
  704. package/dist/runtime/shutdown/terminate.d.ts.map +1 -1
  705. package/dist/runtime/shutdown/terminate.js +83 -56
  706. package/dist/runtime/shutdown/terminate.js.map +1 -1
  707. package/dist/runtime/startup/buffer.d.ts +26 -7
  708. package/dist/runtime/startup/buffer.d.ts.map +1 -1
  709. package/dist/runtime/startup/buffer.js +42 -19
  710. package/dist/runtime/startup/buffer.js.map +1 -1
  711. package/dist/runtime/startup/cleanup.d.ts +37 -11
  712. package/dist/runtime/startup/cleanup.d.ts.map +1 -1
  713. package/dist/runtime/startup/cleanup.js +55 -25
  714. package/dist/runtime/startup/cleanup.js.map +1 -1
  715. package/dist/runtime/startup/index.d.ts +13 -1
  716. package/dist/runtime/startup/index.d.ts.map +1 -1
  717. package/dist/runtime/startup/index.js +38 -6
  718. package/dist/runtime/startup/index.js.map +1 -1
  719. package/dist/runtime/stream-marker.d.ts +17 -0
  720. package/dist/runtime/stream-marker.d.ts.map +1 -0
  721. package/dist/runtime/stream-marker.js +35 -0
  722. package/dist/runtime/stream-marker.js.map +1 -0
  723. package/dist/runtime/update/acquire.d.ts +16 -0
  724. package/dist/runtime/update/acquire.d.ts.map +1 -0
  725. package/dist/runtime/update/acquire.js +62 -0
  726. package/dist/runtime/update/acquire.js.map +1 -0
  727. package/dist/runtime/update/completion.d.ts +12 -0
  728. package/dist/runtime/update/completion.d.ts.map +1 -0
  729. package/dist/runtime/update/completion.js +50 -0
  730. package/dist/runtime/update/completion.js.map +1 -0
  731. package/dist/runtime/update/lock.d.ts +1 -1
  732. package/dist/runtime/update/lock.d.ts.map +1 -1
  733. package/dist/runtime/update/lock.js +21 -38
  734. package/dist/runtime/update/lock.js.map +1 -1
  735. package/dist/runtime/update/once.d.ts +16 -13
  736. package/dist/runtime/update/once.d.ts.map +1 -1
  737. package/dist/runtime/update/once.js +41 -31
  738. package/dist/runtime/update/once.js.map +1 -1
  739. package/dist/runtime/update/owner.js +1 -1
  740. package/dist/runtime/update/retained-lease.js +1 -1
  741. package/dist/runtime/update/waiting.d.ts +14 -1
  742. package/dist/runtime/update/waiting.d.ts.map +1 -1
  743. package/dist/runtime/update/waiting.js +20 -12
  744. package/dist/runtime/update/waiting.js.map +1 -1
  745. package/dist/state/canonical-path.d.ts +5 -0
  746. package/dist/state/canonical-path.d.ts.map +1 -1
  747. package/dist/state/canonical-path.js +11 -5
  748. package/dist/state/canonical-path.js.map +1 -1
  749. package/dist/state/directories.d.ts +31 -1
  750. package/dist/state/directories.d.ts.map +1 -1
  751. package/dist/state/directories.js +75 -13
  752. package/dist/state/directories.js.map +1 -1
  753. package/dist/state/files.d.ts +3 -3
  754. package/dist/state/files.d.ts.map +1 -1
  755. package/dist/state/files.js +4 -10
  756. package/dist/state/files.js.map +1 -1
  757. package/dist/state/launch-ownership.d.ts +4 -2
  758. package/dist/state/launch-ownership.d.ts.map +1 -1
  759. package/dist/state/launch-ownership.js +18 -10
  760. package/dist/state/launch-ownership.js.map +1 -1
  761. package/dist/state/launch-publication.d.ts +7 -1
  762. package/dist/state/launch-publication.d.ts.map +1 -1
  763. package/dist/state/launch-publication.js +14 -2
  764. package/dist/state/launch-publication.js.map +1 -1
  765. package/dist/state/loop-store.d.ts +15 -4
  766. package/dist/state/loop-store.d.ts.map +1 -1
  767. package/dist/state/loop-store.js +13 -7
  768. package/dist/state/loop-store.js.map +1 -1
  769. package/dist/state/preparation/created-directories.d.ts +13 -0
  770. package/dist/state/preparation/created-directories.d.ts.map +1 -0
  771. package/dist/state/preparation/created-directories.js +45 -0
  772. package/dist/state/preparation/created-directories.js.map +1 -0
  773. package/dist/state/preparation/ignore-sync.d.ts +9 -0
  774. package/dist/state/preparation/ignore-sync.d.ts.map +1 -0
  775. package/dist/state/preparation/ignore-sync.js +47 -0
  776. package/dist/state/preparation/ignore-sync.js.map +1 -0
  777. package/dist/state/preparation/index.d.ts +25 -0
  778. package/dist/state/preparation/index.d.ts.map +1 -0
  779. package/dist/state/preparation/index.js +31 -0
  780. package/dist/state/preparation/index.js.map +1 -0
  781. package/dist/state/preparation/install-ignore.d.ts +6 -0
  782. package/dist/state/preparation/install-ignore.d.ts.map +1 -0
  783. package/dist/state/preparation/install-ignore.js +87 -0
  784. package/dist/state/preparation/install-ignore.js.map +1 -0
  785. package/dist/state/preparation/rollback-parent.d.ts +3 -0
  786. package/dist/state/preparation/rollback-parent.d.ts.map +1 -0
  787. package/dist/state/preparation/rollback-parent.js +18 -0
  788. package/dist/state/preparation/rollback-parent.js.map +1 -0
  789. package/dist/state/preparation/root-trust.d.ts +3 -0
  790. package/dist/state/preparation/root-trust.d.ts.map +1 -0
  791. package/dist/state/preparation/root-trust.js +70 -0
  792. package/dist/state/preparation/root-trust.js.map +1 -0
  793. package/dist/state/private-session.d.ts +5 -2
  794. package/dist/state/private-session.d.ts.map +1 -1
  795. package/dist/state/private-session.js +21 -9
  796. package/dist/state/private-session.js.map +1 -1
  797. package/dist/state/root.d.ts +25 -0
  798. package/dist/state/root.d.ts.map +1 -0
  799. package/dist/state/root.js +84 -0
  800. package/dist/state/root.js.map +1 -0
  801. package/dist/state/runtime-paths.d.ts +5 -4
  802. package/dist/state/runtime-paths.d.ts.map +1 -1
  803. package/dist/state/runtime-paths.js +5 -4
  804. package/dist/state/runtime-paths.js.map +1 -1
  805. package/dist/state/setup-failure.d.ts +3 -0
  806. package/dist/state/setup-failure.d.ts.map +1 -0
  807. package/dist/state/setup-failure.js +13 -0
  808. package/dist/state/setup-failure.js.map +1 -0
  809. package/dist/state/socket-home.d.ts +17 -12
  810. package/dist/state/socket-home.d.ts.map +1 -1
  811. package/dist/state/socket-home.js +31 -14
  812. package/dist/state/socket-home.js.map +1 -1
  813. package/dist/state/store.d.ts +1 -3
  814. package/dist/state/store.d.ts.map +1 -1
  815. package/dist/state/store.js +3 -18
  816. package/dist/state/store.js.map +1 -1
  817. package/dist/terminal/headless.d.ts +1 -1
  818. package/dist/terminal/headless.d.ts.map +1 -1
  819. package/dist/terminal/headless.js +1 -3
  820. package/dist/terminal/headless.js.map +1 -1
  821. package/docs/cli-behavior.md +1218 -28
  822. package/docs/cli.md +2 -0
  823. package/docs/codex/cli.md +4 -2
  824. package/package.json +4 -4
  825. package/prd/04-architecture.md +32 -0
  826. package/prd/05-api/5.1-starting-claude.md +17 -1
  827. package/prd/05-api/5.2-resuming-claude.md +5 -0
  828. package/prd/05-api/5.3-claude-session-api.md +267 -40
  829. package/prd/05-api/5.4-events.md +77 -10
  830. package/prd/05-api/5.5-starting-codex.md +3 -1
  831. package/prd/05-api/5.6-resuming-codex.md +4 -1
  832. package/prd/05-api/5.7-codex-session-api.md +87 -5
  833. package/prd/05-api/5.8-session-api.md +32 -1
  834. package/prd/06-claude-hook-bridge.md +2 -0
  835. package/prd/07a-codex-hook-bridge.md +5 -0
  836. package/prd/08-state.md +63 -7
  837. package/prd/09-lifecycle.md +126 -35
  838. package/prd/10-errors.md +11 -4
  839. package/prd/12-e2e.md +81 -0
  840. package/prd/12a-cli.md +63 -9
  841. package/prd/14-conformance.md +52 -35
  842. package/prd/16-maintainer-review.md +11 -0
  843. package/dist/claude/model-composer.d.ts +0 -2
  844. package/dist/claude/model-composer.d.ts.map +0 -1
  845. package/dist/claude/model-composer.js +0 -12
  846. package/dist/claude/model-composer.js.map +0 -1
  847. package/dist/claude/transcript/drain.d.ts +0 -33
  848. package/dist/claude/transcript/drain.d.ts.map +0 -1
  849. package/dist/claude/transcript/drain.js +0 -56
  850. package/dist/claude/transcript/drain.js.map +0 -1
  851. package/dist/codex/session/cli-exit.d.ts +0 -24
  852. package/dist/codex/session/cli-exit.d.ts.map +0 -1
  853. package/dist/codex/session/cli-exit.js +0 -43
  854. package/dist/codex/session/cli-exit.js.map +0 -1
  855. package/dist/codex/transcript/drain.d.ts +0 -30
  856. package/dist/codex/transcript/drain.d.ts.map +0 -1
  857. package/dist/codex/transcript/drain.js +0 -48
  858. package/dist/codex/transcript/drain.js.map +0 -1
  859. package/dist/runtime/probe-cleanup.d.ts.map +0 -1
  860. package/dist/runtime/probe-cleanup.js.map +0 -1
  861. package/dist/runtime/probe.d.ts +0 -11
  862. package/dist/runtime/probe.d.ts.map +0 -1
  863. package/dist/runtime/probe.js +0 -113
  864. package/dist/runtime/probe.js.map +0 -1
  865. package/dist/runtime/session/foreign-picker.d.ts +0 -8
  866. package/dist/runtime/session/foreign-picker.d.ts.map +0 -1
  867. package/dist/runtime/session/foreign-picker.js +0 -18
  868. package/dist/runtime/session/foreign-picker.js.map +0 -1
  869. package/dist/runtime/session/picker-input.d.ts +0 -17
  870. package/dist/runtime/session/picker-input.d.ts.map +0 -1
  871. package/dist/runtime/session/picker-input.js.map +0 -1
@@ -55,8 +55,28 @@ invalid and will conclude the hook "never fires". (This once produced a false
55
55
  sandboxed homes, and user `config.toml` hooks merge additively with `-c` hooks.)
56
56
  Because `SessionStart` is Elwood's authoritative Codex readiness signal
57
57
  (`markInitialReadyFromHook`, `src/codex/session/hooks.ts`), cold-start readiness
58
- comes from that hook, bounded by a **10 s starvation deadline** armed on the
59
- first render frame (`src/runtime/readiness/initial-ready.ts`, `maxWaitMs = 10_000`). C-API-28.
58
+ comes from that hook, bounded by a **two-stage starvation deadline** armed on the
59
+ first render frame (`src/runtime/readiness/initial-ready.ts`): after 10 s
60
+ (`initialDeadlineMs`) Codex readiness waits for a configured composer, described
61
+ below, and after 60 s (`initialBackstopMs`) it is released without one. C-API-28.
62
+
63
+ **Codex 0.164 paints a boot composer before its session is configured
64
+ (2026-10-25).** About 0.6 s after its first output Codex draws `› Ask Codex to do
65
+ anything` and `? for shortcuts` with no model footer; the header may read
66
+ `loading` or already show the path. The model footer (`GPT-6.1-Sol default ·
67
+ <path>`) appears only once the session is configured, which for an untrusted
68
+ folder is after the directory-trust gate is answered. On a loaded machine (load
69
+ average 20 to 127, 8 concurrent launches with `autotrust`, fresh `git init`
70
+ folders) the gate painted 11.1 to 30.6 s after the first output in 24 of 32
71
+ launches, and every one of those 24 reported `ready` at 10.0 s, onto a screen
72
+ showing the boot composer without a footer. The other 8 painted their gate at
73
+ 8.9 to 9.0 s and readied after it, on footed composers. Unloaded, the gate came
74
+ 3.4 to 3.9 s after the first output. Codex's deadline therefore marks only onto a
75
+ composer with its model footer (`codexComposerConfigured`,
76
+ `src/codex/screen/clearance.ts`), and a 60 s backstop, twice the longest measured
77
+ gate delay, releases readiness if no footer is ever recognized; a gate painting
78
+ later than that would still follow `ready`. Fixtures:
79
+ `tests/fixtures/codex-0.164.0/boot/`. C-API-28.
60
80
 
61
81
  **Codex 0.153.3 can still swallow the deadline-released first paste under load.**
62
82
  Its cold composer may accept the paste visually, repaint another startup spinner,
@@ -119,6 +139,38 @@ misattributed to a CLI regression.)
119
139
  quiet frame is not proof it finished. Evidence-driven turns (a caller
120
140
  submission, a hook) bypass settling immediately. `src/core/turn-state.ts`,
121
141
  C-TURN-03.
142
+ - **Codex 0.164.0 paints a `Resuming session…` placeholder before it loads the
143
+ conversation (2026-10-25).** Five captured resumes in private Codex homes, at load
144
+ averages of 20 to 60, all had the same shape. First comes the header, then
145
+ `Resuming session…`, then the live composer and `? for shortcuts`. That frame
146
+ repaints 8 to 11 times with no visible change. Elwood latches resume readiness on
147
+ its first composer frame. 0.5 to 1.6 s later, Codex sets a braille title spinner
148
+ and `OSC 7501;state=working:app=codex`. It replaces the placeholder with the
149
+ replayed transcript, either in the spinner's first frame or one frame later. The
150
+ spinner runs 0.7 to 1.5 s. Then the title goes idle with `state=idle`, at most one
151
+ cursor repaint follows, and Codex sends nothing more until input arrives. The quiet streak counted the
152
+ placeholder's repaints and released settling before the load. The spinner then
153
+ became a phantom `running → ready` on every capture. The real-CLI e2e reported it
154
+ as `saw: running,ready`. Placeholder frames are no longer quiet. Once the
155
+ placeholder has painted and the load's spinner has been seen, the first quiet
156
+ composer ends settling. A five-frame streak never arrives from an idle Codex.
157
+ Captures: `tests/fixtures/codex-0.164.0/resume/`. C-TURN-03.
158
+ - **Codex records a turn's completion only after its `Stop` hook returns
159
+ (2026-10-25).** Elwood reports `ready` from that hook, so a host that stops the
160
+ session at that `ready` signals Codex 0.164.0 before it writes the turn's
161
+ `task_complete`, and Codex exits without it. Stopping in the `ready` listener lost
162
+ the record in 3 of 3 probe runs and in 2 of 2 runs of
163
+ `tests/e2e/codex-stop-commit.e2e.ts` on `main`. Stopping 66 ms later lost it too;
164
+ at 156 ms and at 1 s it was written. The record carries the hook's `turn_id`, and
165
+ its own timestamp falls 3 to 94 ms after the hook reached Elwood. Resuming such a
166
+ conversation replays it ending in
167
+ `■ Conversation interrupted - use /feedback if something went wrong`. Before the
168
+ placeholder fix, that banner closed the spinner's phantom turn early and a second
169
+ phantom followed: `saw: running,ready,running,ready`. The `uncommitted-turn`
170
+ capture retains that replay. `stop()` and `teardown()` now give Codex up to 2 s to
171
+ write the record before signaling it. With the wait, a stop at `ready` took 70 to 126 ms
172
+ instead of 13 to 29 ms, and the record was always written.
173
+ `src/codex/session/turn-commit.ts`, C-TURN-03, C-LIFE-13.
122
174
  - **A caller submission can precede the resumed composer's repaint.** The old
123
175
  idle composer may remain visible briefly after `caller_submitted`; that frame
124
176
  is not a turn end. Resume settling now synchronizes to current rendered work
@@ -143,6 +195,23 @@ misattributed to a CLI regression.)
143
195
  observed `◐ ` prefix alongside the existing braille range; both screen facts
144
196
  and live trust clearance share it. No other new spinner glyph is inferred.
145
197
  C-TURN-05, C-TRUST-01.
198
+ - **Claude 2.1.310 alternates its working title between `◐` and `◑`, and its
199
+ spinner row has no `esc to interrupt`.** Sampling the title every 50 ms through
200
+ real turns (2026-10-25, Opus 5.5, 189 columns) gave `✳ ◐ ◑ ◐ ✳` and
201
+ `✳ ◐ ◑ ◐ ◑ ◐ ✳`, each half circle lasting about a second, with `✳` while idle.
202
+ The spinner row reads `✢ Nebulizing… (2s)`, or
203
+ `✶ Computing… (running Stop hook · 8s · ↓ 3 tokens)`, and no frame of those turns
204
+ contained `esc to interrupt`, so the title was the only working signal. With only
205
+ `◐` recognized, every `◑` frame read as idle: the session reported
206
+ `running → ready → running` about once a second for the whole turn, eight false
207
+ `ready`s during one 6 s `sleep` tool call. The ergonomic turn treats an accepted
208
+ `ready` as the start of its 2 s quiet window, so a turn with more than 2 s between
209
+ a `◑` frame and its first text settled early. Six concurrent `send("Reply with
210
+ exactly OK…")` calls returned `""` in 5 of 6 at load 117 and 6 of 6 at load 207,
211
+ with Claude's `Stop` carrying `OK` 2–4 s later. Both half circles now read as
212
+ working (`tests/fixtures/claude-2.1.310/working-half-right.txt`): the same six
213
+ sends returned `OK` 6 of 6 at load 120–126 with no `ready` before `Stop`.
214
+ C-TURN-05, C-API-48.
146
215
  - Both CLIs render the literal `esc to interrupt` only while a turn is running
147
216
  (Claude footer, Codex "Working" spinner). Elwood's `TurnStateWatcher` keys off
148
217
  this, and **arms only after initial readiness** because the Codex MCP boot
@@ -159,6 +228,64 @@ misattributed to a CLI regression.)
159
228
  after done — do not treat spinner presence as a reliable per-frame liveness bit
160
229
  at tiny sizes.
161
230
 
231
+ ## Rejected turns (2026-10-25)
232
+
233
+ **Claude 2.1.310 ends a rejected turn with `StopFailure`, never `Stop`.** A bogus
234
+ `--model elwood-bogus-model` is refused before any model quota is consumed. The
235
+ hooks were `InstructionsLoaded`, `SessionStart` (`model: "elwood-bogus-model"`),
236
+ `UserPromptSubmit`, then `StopFailure` with `error: "model_not_found"`, a
237
+ `prompt_id` matching the submit, and `last_assistant_message` set to Claude's own
238
+ "There's an issue with the selected model (elwood-bogus-model)…" text. No `Stop`
239
+ fired. The transcript writes that same text as an `assistant` entry carrying
240
+ `isApiErrorMessage: true`, `error: "model_not_found"` and `message.model:
241
+ "<synthetic>"`, so Elwood's transcript activity surfaces it as an ordinary
242
+ `assistant_message`. Startup also shows `⚠ Model "elwood-bogus-model" isn't
243
+ recognized`, so the session reaches ready normally. Before C-API-59 the
244
+ no-oracle quiet window settled that turn as a success: `ClaudeSession.send`
245
+ resolved with the error text and `elwood run` exited 0 with it as `response`.
246
+ Elwood now reads `StopFailure` as rejection evidence (`claudeBoundarySignal`) and
247
+ fails the turn with `turn_failed`, message `Claude rejected the turn:
248
+ model_not_found`. The assistant text is not the diagnostic (§6.1). C-API-59,
249
+ C-CLI-28, C-E2E-20.
250
+
251
+ **The hook can lag the first rendered `ready` under load.** On an idle machine
252
+ `StopFailure` arrived in the same 0.1 s as `ready` and 0.3 s before the transcript
253
+ entry. With four concurrent sessions at load ~55, it lagged the first `ready` by
254
+ 0.8, 2.0, 2.5 and 1.8 s. One run rendered a transient `ready → running → ready`
255
+ before the hook. The turn fails only if the hook arrives before the turn settles,
256
+ and a no-oracle turn settles 2 s after its last content. That is the same lag
257
+ that can end a successful turn early when its `Stop` is late. A hook that late can
258
+ also land while the NEXT turn is running. Each hook is delivered by its own bridge
259
+ process, so the next turn's `UserPromptSubmit` can even arrive first. Elwood
260
+ therefore applies a `StopFailure` only after the turn's own `UserPromptSubmit`, and
261
+ only when its `prompt_id` matches that submit's.
262
+
263
+ **Codex 0.164.0 rejects the same bogus model without any boundary hook.** In a
264
+ sandboxed `CODEX_HOME`, only `SessionStart` and `UserPromptSubmit` fired. The
265
+ evidence is a transcript `event_msg` `task_complete` carrying `error`, with
266
+ `last_agent_message: null`, as first measured on 0.155.0. The record's `turn_id`
267
+ (and `root_turn_id`) equal the `UserPromptSubmit` `turn_id`. `error.message` is the
268
+ service's raw JSON body (`{"type":"error","status":400,"error":{...,"code":
269
+ "model_not_found"}}`, "…model is not supported when using Codex with a ChatGPT
270
+ account."), and `error.codex_error_info` is `"other"`. The binary's
271
+ `CodexErrorInfo` kinds are bare strings (`usage_limit_exceeded`, `bad_request`,
272
+ `other`, …) or one-key objects for kinds with fields (`http_connection_failed`,
273
+ `response_stream_connection_failed`, `response_stream_disconnected`, …). The
274
+ screen still reached `ready` 1.4 s after the submit hook and 0.15 s before the record.
275
+
276
+ On 0.164.0 the turn binds the submit's `turn_id`, and only a private `Stop` or a
277
+ classified rendered completion ends it. A rejected turn has no `Stop`, so its end
278
+ depended on the rendered path. In the A/B runs (load 60–100), `CodexSession.send`
279
+ resolved `""` about 20 s after submission, and `elwood run` exited 0 with
280
+ `"response": ""`, the same empty success 0.155.0 showed. In one earlier probe, run
281
+ beside a full test suite at load ~100, `send` stayed pending for over five minutes
282
+ and resolved `""` only when the CLI was killed. Elwood now
283
+ reads a failed `task_complete` that names the accepted `turn_id` as that turn's
284
+ native boundary (`confirmCodexRejection`) and fails it with `turn_failed`. The
285
+ message is `error.message` and `details.agentError` is the `codex_error_info`
286
+ kind. A second turn on the same session is rejected the same way, so the first
287
+ released the session at its real boundary. C-API-60, C-CLI-28, C-E2E-21.
288
+
162
289
  ## Codex transcript replies
163
290
 
164
291
  **Codex 0.149.1 writes committed replies as phased `message` response items, not
@@ -181,6 +308,127 @@ only their raw observation. C-CODEX-16, C-CODEX-22.
181
308
  visible catalog; every returned row correctly had `isCurrent: false`.
182
309
  Real picker tests should let the installed CLI choose its launch default,
183
310
  rather than pinning a model that can disappear from the catalog.
311
+ - **Codex 0.164.0 marks only the `(current)` row; nothing on screen names the
312
+ default.** Probed 2026-10-25 in a sandboxed `CODEX_HOME` whose config sets
313
+ `model = "gpt-6.1-sol"`, with the session launched on `--model gpt-6-luna`: the
314
+ picker drew `› 4. GPT-6-Luna (current)` and no marker on GPT-6.1-Sol. A session
315
+ switch moves `(current)` to the new row, again with nothing on the launch model.
316
+ Older Codex drew `1. gpt-5.5 (default)` beside `2. gpt-5.4 (current)`
317
+ (`codexPickerSplitMarkers`), so reading "a lone `(current)` row is also the
318
+ default" reported the session's own model as the user's default. Elwood now reads
319
+ the default from `config.toml`. The cached catalog (`models_cache.json`) carries
320
+ no default flag, so Codex's built-in default in an unconfigured home cannot be
321
+ identified at all.
322
+ - **Claude scrolls a list longer than ten rows inside the picker (2.1.310, 2026-10-24).**
323
+ An account with 13 models renders a ten-row window. At the top, rows 1–9 are plain,
324
+ row 10 is `↓ 10. Opus 4.8 …`, and `… +3 models` stands directly under it. Moving the
325
+ cursor past the last visible row scrolls the window by one: the first visible row then
326
+ carries `↑` (`↑ 2.`), and the last carries `↓` with `… +2 models` / `… +1 model` below it
327
+ while any rows stay hidden; at the end of the list that line is blank. The cursor glyph
328
+ REPLACES a marker on its row (`❯ 11. Opus 4.7` above `… +2 models`), and moving up keeps
329
+ one row visible above the cursor, so arrowing from row 5 to row 4 shifts the window from
330
+ `↑ 4.`–`13.` to `↑ 3.`–`↓ 12.`. The picker OPENS scrolled when the current model is below
331
+ the fold: started on `claude-opus-4-6`, it showed `↑ 3.` through `❯ 12. Opus 4.6 ✔` and
332
+ `… +1 model`, with the `Default (recommended)` row out of view. Two consequences: the
333
+ region grammar must accept a block that starts at its `↑` row and ends at its `↓` row or
334
+ overflow line (before this, it rejected every frame of such a list, so `setModel` failed
335
+ on its first arrow key with "The model picker was replaced"), and `setModel` must settle
336
+ on the cursor row's identity, not its index, because a target's index changes as the
337
+ window scrolls under the cursor. Fixtures:
338
+ `tests/fixtures/claude-2.1.310/model-picker/`. C-API-23, C-API-24.
339
+ - **Arrow keys walk Claude's picker window without applying anything (2.1.310, 2026-10-25).**
340
+ Row numbers are absolute positions, so a window starting past row 1 hides the rows
341
+ above it and `… +K models` counts the rows below; `listModels` reads the whole list by
342
+ walking the cursor one row at a time and merging the windows by number. Measured on a
343
+ 13-model account: the window scrolls DOWN only once the cursor passes its last row, keeping
344
+ the cursor ON the last visible row (`↑ 2.` … `❯ 11.`), but scrolls UP while one row is
345
+ still above the cursor (`↑ 3.` `❯ 4.`), so the cursor never sits on the first row of a
346
+ scrolled window. Both ends WRAP: `↓` on row 13 jumps to `❯ 1.` with the top window, and
347
+ `↑` on row 1 jumps to `❯ 13.` with `↑ 4.`–`13.`; Elwood never presses past an end. Opened
348
+ at the top, 12 `↓` reveal rows 11–13; opened scrolled on row 12, one `↓` and 11 `↑`
349
+ reveal rows 13, 2, and 1. After 28 arrow presses, Escape printed `⎿ Kept model as Opus
350
+ 5.5 (default)`, the header kept `with xhigh effort`, and the reopened picker showed the
351
+ same current row and effort, even though the effort row had followed the highlight
352
+ (`◐`, `●`, `○ Effort not supported`) on the way. A session started with `--name` adds a
353
+ rule carrying the name above the picker and walks identically. Fixtures:
354
+ `walk-down-from-top.txt`, `walk-opened-scrolled.txt` (frames separated by `--- ↓`/`--- ↑`,
355
+ each the screen after that key). A move that scrolls the window can be read before its
356
+ overflow line is repainted: a real listing that read on every render (2026-10-25) read
357
+ the move to `❯ 11.` with a `… +K models` count that was missing or did not add up to 13,
358
+ so the walk waits for a frame whose count adds up instead of failing on that one.
359
+ The raw stream shows why: the renderer writes only the cells that change, top to bottom,
360
+ and the PTY delivers the paint in reads of about 1 KB, so a frame can be read between
361
+ two reads. Scrolling to row 11 arrived as a 1,020-byte read carrying the new rows and a
362
+ 71-byte read carrying the `3`→`2` of the count. The opening paint is ordered the same way:
363
+ the `… +K models` line is written before the effort row and the footer, and Elwood reads
364
+ the picker as complete only once its `Esc to cancel` footer is on screen, so a complete
365
+ frame always carries its count. Replaying every byte prefix of two real listings (opened
366
+ on row 2 and walked to 13; opened on row 10, where a count-less frame would read as a
367
+ complete ten-row list) found no complete frame stating fewer than 13 rows; the only
368
+ inconsistent ones were the stale-count frames above (`claude-picker-paint-order.test.ts`
369
+ pins the row-10 opening, `opening-on-row-10.json`, captured paint bytes over a placeholder
370
+ screen). If a later Claude painted the footer before the count, the walk could take a
371
+ full window with the cursor on its last row for the whole list: a listing would come back
372
+ short, and `setModel` would reject a hidden id as unknown. No key would go anywhere else,
373
+ since every key is still gated on the operation's own complete picker. C-API-23, C-API-24.
374
+ - **The picker's effort row follows the HIGHLIGHTED model, and `●` is one of its glyphs.**
375
+ On 2.1.310 the row under the list reads `○ Low effort`, `◐ Medium effort`, `● High
376
+ effort`, `◉ xHigh effort`, or `◈ Max effort` (each `←/→ to adjust`, with `(default)` when
377
+ it is that model's default), and `○ Effort not supported for Haiku 4.5` for a model
378
+ without effort. It changes as the cursor moves, so navigation crosses several glyphs.
379
+ `●` is also the older Claude reply bullet, which the region grammar treats as foreign
380
+ content, so the control row is recognized by its own copy before the reply check;
381
+ otherwise every frame with the cursor on a High-effort model reads as transcript. Captured
382
+ with `--effort low|medium|high|max` across all 13 rows. C-API-24.
383
+ - **Claude paints `/model` over its slash-command menu, so the header lands first.** A
384
+ 5 ms capture on 2.1.310 (2026-10-24) showed the `Select model` header for 10–500 ms above
385
+ a partial dialog: rows 1–3 with row 3 spliced into a stale `/review-…` suggestion and
386
+ the rest of the menu below it, rows 1–7 the same way, or all ten rows and the overflow
387
+ line above leftover menu prose and no footer yet. Parsing the first frame that showed
388
+ the header returned one row, three rows, or none (`listClaudeModels` in `~/.coal-harbor`
389
+ gave 9, 1, error, 9, error over five runs), and Coal Harbor persisted a one-row
390
+ `default` catalog as a successful discovery. The picker is therefore parsed only once
391
+ the region grammar recognizes the complete dialog, footer included — the same check
392
+ every key write passes. A picker that never completes is never written to, because a
393
+ bare header is untrusted (the agent can print one): the operation fails, and failure
394
+ cleanup Escapes the dialog once it is recognizable. Cleanup's release is not proof the
395
+ picker is gone either: a blank or half-painted frame matches neither grammar, and the
396
+ picker can paint again after it. So once the operation or its cleanup has seen its
397
+ dialog, or while the picker header is up, a cleanup that ends on anything short of a
398
+ positively clear composer keeps queued input held until the composer is clear: Enter on
399
+ that picker would apply its highlighted row. Only a dialog the caller owns replacing it,
400
+ a complete `PreModelSwitch` hook confirmation, ends that hold early; the hook's prompt is
401
+ left for the caller. The header only ever holds; it never authorizes a key. Fixtures: `tests/fixtures/claude-2.1.310/model-picker/painting-*`.
402
+ C-API-23, C-API-24, C-API-55.
403
+ - **Claude can list a compiled five-row picker instead of the served catalog (2.1.310,
404
+ 2026-10-25).** Observed: launched with `CLAUDE_CODE_MODEL_CATALOG=0`, the picker lists
405
+ `1. Default (recommended)`, `2. Opus`, `3. Fable`, `4. Sonnet`, and `5. Haiku`. Those are
406
+ bare family names with no version, and there is no overflow line. Every listing in that
407
+ process returns the same five rows, and Elwood reads them correctly. A listing e2e on a
408
+ loaded machine got the same five rows for its whole session, with no catalog variable in
409
+ its environment (the e2e harness strips `CLAUDE_CODE_*`).
410
+
411
+ From the 2.1.310 bundle, not observed: the choice is made once per process. The catalog
412
+ is off when `CLAUDE_CODE_MODEL_CATALOG` is falsy, under `--bare`, with nonessential
413
+ traffic disabled, with `ANTHROPIC_UNIX_SOCKET` set, with a provider that is not
414
+ first-party, with auth that has no catalog scope, when policy disallows
415
+ `allow_model_catalog`, or when the `tengu_delegated_quail` flag is off. With no catalog
416
+ cached in `~/.claude/cache/model-catalog/`, a first fetch slower than 1,750 ms (1,500
417
+ headless) also falls back.
418
+
419
+ The fallback has no hidden rows, so the listing e2e can't test the window walk against
420
+ it. Instead of retrying, the e2e fails with these causes
421
+ (`tests/e2e/claude-compiled-fallback.ts`), so a degraded environment stays visible.
422
+ C-API-23.
423
+ - **Codex 0.164.0 shortened its dialog hints.** The picker closes with `enter select ·
424
+ esc back` and the reasoning screen with `enter default · s session · esc back` (one
425
+ Escape still returns to the picker). Neither matched the old closing-hint phrases
426
+ (`esc to cancel`, `esc to go back`), so the grammar never recognized either dialog:
427
+ `setModel` failed on its first key with "The model picker was replaced", and only the
428
+ header-parsing `listModels` kept working. Each CLI now supplies its own hint phrase,
429
+ so Claude's `Esc to cancel` no longer closes a Codex dialog either. The new `s session`
430
+ key is a session-only apply. Fixtures: `tests/fixtures/codex-0.164.0/model-picker/`.
431
+ C-API-23, C-API-24.
184
432
  - **A model dialog replaces the composer; nothing renders below it.** Full-viewport
185
433
  captures from Claude 2.1.274 and Codex 0.154.0 (2026-09-17,
186
434
  `tests/helpers/model-dialog-viewports.ts`) show the picker, Codex's reasoning
@@ -197,12 +445,14 @@ only their raw observation. C-CODEX-16, C-CODEX-22.
197
445
  phrase on a reply or composer row itself (`❯ Select model` is staged input), and
198
446
  a quoted picker above another dialog. Below its header a native dialog holds
199
447
  exactly one contiguous block of picker rows (description column, numbered from 1,
200
- at most one cursor); any other caret or numbered row (`❯ 1. Yes`, ` 1. Yes`, a
201
- staged `❯ 1. fix this then that`) restarts the numbering, follows the blank or
202
- rule that separates the composer, or lacks the column, and marks the region as
203
- someone else's. The block must also be COMPLETE before Elwood treats it as live:
204
- every captured picker and reasoning screen carries at least two numbered rows and
205
- a closing hint row (`Esc to cancel`, `esc to go back`). A header with neither —
448
+ at most one cursor); the one exception is a scrolled Claude window, which starts at
449
+ its `↑`-marked row (see the windowed list below). Any other caret or numbered row
450
+ (`❯ 1. Yes`, ` 1. Yes`, a staged `❯ 1. fix this then that`) restarts the
451
+ numbering, follows the blank or rule that separates the composer, or lacks the
452
+ column, and marks the region as someone else's. The block must also be COMPLETE
453
+ before Elwood treats it as live: every captured picker and reasoning screen carries
454
+ at least two numbered rows and a closing hint row (`Esc to cancel`, `esc to go
455
+ back`, `esc back`). A header with neither —
206
456
  a transcript quoting `Select model` above a trust or hook prompt, or above a
207
457
  reply's own numbered list — is a fragment, and Escaping it would dismiss the live
208
458
  prompt underneath. The CURSOR is deliberately not required: a complete picker
@@ -281,6 +531,45 @@ only their raw observation. C-CODEX-16, C-CODEX-22.
281
531
  the returned composer. C-API-24, C-ATTN-04. Verified from the installed 2.1.258
282
532
  native binary and field-captured model/effort dialogs.
283
533
 
534
+ ## Login-shell `CODEX_HOME` (2026-10-25)
535
+
536
+ Codex runs as `$SHELL -l -i -c 'exec codex …'` (§4.2). The login shell inherits
537
+ Elwood's environment and the user's startup files can change it, so what decides
538
+ which `config.toml` Codex reads and the picker writes is the environment after
539
+ those files run, not Elwood's own.
540
+ Codex takes a non-empty `CODEX_HOME`, canonicalized (a relative one from the
541
+ directory it runs in), else `$HOME/.codex`. Startup files can make that answer
542
+ depend on the launch itself:
543
+
544
+ - **`$PWD`.** The login shell starts in the session `cwd`, so
545
+ `export CODEX_HOME="$PWD/codex-home"` gives every project its own home. Real Codex
546
+ 0.164.0 in two projects under that profile kept each project's default and restore
547
+ in that project's home.
548
+ - **The terminal and the session's environment.** Only the PTY launch has a TTY on
549
+ stdin and `ELWOOD_SESSION_ID`. A second shell started to ask (the first version of
550
+ this fix ran `printf` in a separate login shell) has neither, so a profile that
551
+ branches on `[[ -t 0 ]]` or on a session variable answers for a different home. The
552
+ real e2e's profile exports the session home only when both are present.
553
+ - **Exported values only.** Codex sees only exported variables. A profile that
554
+ assigns `CODEX_HOME=…` without `export` changes what Codex sees only if the
555
+ variable was already exported, for example inherited from Elwood's environment.
556
+ Otherwise Codex keeps the inherited value, or none. The report is written by a
557
+ child `/bin/sh`, which sees exactly what `codex` inherits.
558
+ - **A symlinked `cwd`.** The kernel follows a symlink before a later `..`, and so
559
+ does Codex. From `feature-link -> worktrees/feature`, real Codex 0.164.0 with
560
+ `CODEX_HOME=../codex-home` ran with `worktrees/codex-home`: its picker wrote that
561
+ file, not the `codex-home` beside the link. Resolving the value by name against
562
+ the session `cwd` read and restored the wrong file.
563
+
564
+ Elwood therefore has the launch script write the answer just before `exec codex`:
565
+ `/bin/sh -c 'printf "%s\0%s\0%s\0" "$CODEX_HOME" "$HOME" "$PWD"' > <report> || exit 73; exec codex …`,
566
+ with the report in the session's socket home, named after the launch's socket. The
567
+ session canonicalizes a non-empty home with `realpath` on the unnormalized
568
+ `$PWD/$CODEX_HOME`, which follows links the way the kernel does. Profile output
569
+ (banners, `nvm` notices) goes to the terminal, never to the report, so nothing has to
570
+ be parsed out of shell noise. If the report cannot be written, the launch shell exits
571
+ with 73 and never starts Codex. C-CODEX-24.
572
+
284
573
  ## Trust prompts
285
574
 
286
575
  The real Claude folder-trust prompt renders as **one dialog** spanning header →
@@ -290,8 +579,10 @@ across physical rows** on a narrow terminal. Its option format is version-couple
290
579
  - Claude 2.1.206 used numbered rows with the affirmative first
291
580
  (`❯ 1. Yes, I trust this folder / 2. No, exit`).
292
581
  - Claude 2.1.252 uses unnumbered cursor rows with the safe decline selected first
293
- (`❯ No, exit / Yes, I trust this folder`). Accepting requires ArrowDown + Enter;
294
- sending the old numeric answer leaves the dialog waiting.
582
+ (`❯ No, exit / Yes, I trust this folder`). Accepting requires moving the cursor
583
+ to the affirmative and pressing Enter; sending the old numeric answer leaves the
584
+ dialog waiting. From 2.1.310 evidence, never do that with a relative arrow and a
585
+ separate Enter (see "Claude trust selection reset" below).
295
586
 
296
587
  The workspace gate is independent of tool permission policy: it still appears
297
588
  under both `--permission-mode bypassPermissions` and the exact
@@ -304,7 +595,8 @@ under both `--permission-mode bypassPermissions` and the exact
304
595
  "Yes" option) must never anchor a prompt (anti-spoofing).
305
596
  - treat the whole numbered or cursor-selectable block as the option region,
306
597
  including unselected rows above the cursor, then either send the old option
307
- number or navigate from the rendered cursor to the affirmative and press Enter.
598
+ number or select the affirmative by its absolute row and confirm in the same
599
+ write.
308
600
 
309
601
  Under `autotrust`, detect-and-approve: if a trust prompt is detected, answer yes
310
602
  rather than leave the agent hanging on the gate. Keep the cheap safety: never
@@ -326,6 +618,45 @@ cover both `autotrust` settings. A native rerun with this grammar answered the g
326
618
  and attached the first image; its later cancelled-draft cleanup failed separately,
327
619
  so this is trust/attachment evidence, not a passing successor-cleanup run. C-TRUST-01.
328
620
 
621
+ **Codex 0.164.0 asked for folder trust only in a Git repository, and saved the answer to
622
+ `CODEX_HOME`.** Real launches on 2026-10-25, each with a fresh `CODEX_HOME`: in a plain
623
+ scratch directory, Codex reached `ready` and wrote no trust entry, with and without
624
+ `--sandbox read-only --ask-for-approval never` and with and without a seeded
625
+ `config.toml`; no trust gate rendered in the launches whose screens were captured. After
626
+ `git init` in the same kind of directory it showed `Folder access` with `Note: You’re in
627
+ a subdirectory of a Git project. Trusting will apply to the repository root:` and the
628
+ resolved path (`/private/var/...` for a `/var/...` cwd). `Trust and continue` then saved
629
+ `[projects."<that path>"]` with `trust_level = "trusted"` to `$CODEX_HOME/config.toml`.
630
+ Every launch also wrote `[tui] screen_reader_detection_done = true` there, plus state
631
+ and log databases, a models cache, and plugin and skill caches. An e2e that leaves
632
+ `CODEX_HOME` unset therefore writes into the developer's own `~/.codex`, so the suite
633
+ gives every process a private one (`tests/e2e/codex-home.ts`, C-E2E-22).
634
+
635
+ **Codex 0.164.0 draws its trust gates differently when hook trust is not bypassed
636
+ (2026-10-25).** Native launches at 100 × 40 in a `git init` scratch directory with a
637
+ fresh `CODEX_HOME`:
638
+
639
+ - With `--dangerously-bypass-hook-trust` or `-c hookTrust="trust-all"` (Elwood passes
640
+ one of them on every launch, `src/codex/command.ts`), the `Folder access` gate is the
641
+ 0.156.1 layout: `1. Trust and continue`, `2. Quit`, and `enter continue · esc quit`.
642
+ No hook gate follows.
643
+ - With neither, as C-E2E-09 launches Codex to see both gates, the same gate's decline
644
+ reads `2. Back to Agent Command Center` and its footer is `enter continue · esc back`.
645
+ After `Trust and continue`, `Hooks need review` follows with `1 hook is new or
646
+ changed.`, `Hooks can run outside the sandbox after you trust them.`, `1. Review
647
+ hooks`, `2. Trust all and continue`, `3. Continue without trusting (hooks won't
648
+ run)`, and `enter confirm · esc skip`. Alternate screen or `--no-alt-screen` drew
649
+ the same rows.
650
+
651
+ Without this grammar the unknown option and footers left both gates' option tails
652
+ invalid, so the responder held each gate and never answered it. The two footers are
653
+ bound to their gates (`TrustPromptSpec.footer`): each ends a dialog only under the
654
+ header, explanation, and options of the gate that draws it, and never counts as one of
655
+ the generic native footers that let an off-allowlist gate hold input. Under any other
656
+ dialog, or in conversation text, they make no frame a trust gate. The sanitized
657
+ captures, with the bypass and without, are `tests/fixtures/codex-0.164.0/trust/`.
658
+ C-CODEX-15.
659
+
329
660
  ### Where the clearance grammar lives
330
661
 
331
662
  "This frame is the CLI's own idle composer, so the gate cleared" is a per-CLI layout
@@ -410,7 +741,9 @@ replacement prompts or future Claude renderers.
410
741
  Claude 2.1.278 can paint the complete folder-trust dialog before accepting keyboard
411
742
  navigation. In the real human-trust test, immediate Down/Enter writes left the
412
743
  selection on "No, exit". Waiting for the selected "Yes, I trust this folder" repaint
413
- before Enter cleared the dialog. The test records physical PTY writes and confirms
744
+ before Enter cleared the dialog. The 2.1.310 investigation below found the cause, an
745
+ input guard that remounts the dialog, and showed that waiting for the repaint is not
746
+ enough on a loaded machine. The test records physical PTY writes and confirms
414
747
  that queued caller input follows native cursor clearance; it does not require a
415
748
  model response or available turn quota.
416
749
 
@@ -475,11 +808,12 @@ high-trust probe, a manual repeat at eight seconds cleared that same gate; the
475
808
  first write had already produced premature startup activity at roughly one second.
476
809
  Numbered automation therefore retries every 250 ms for at most five seconds,
477
810
  revalidates the same header and exact option identity before each write, and
478
- reports answered only after the gate clears. A native directory-to-hook transition
811
+ reports answered only after the gate clears. Only the first write carries the
812
+ number; see "Codex numbered trust retries" below for what a retry may send. A native directory-to-hook transition
479
813
  confirms directory clearance without sending an old answer to the hook gate.
480
814
  An observed clear-and-reappear generation cancels the old attempt.
481
815
 
482
- Cursor navigation rereads between individual arrows and Enter. Both navigation
816
+ Cursor answers reread the frame before every selection and confirmation write. Both navigation
483
817
  styles now belong to one session-owned coordinator: each observed dialog
484
818
  generation owns its reservation, deadline, exact affirmative, and completion.
485
819
  An old cancelled attempt cannot release or settle a reappeared gate. The five-
@@ -503,6 +837,68 @@ PTY exit cancel pending automation and timers before disposal. Only a rejected
503
837
  PTY write belonging to a still-live matching attempt emits
504
838
  `startup_prompt_write_failed`; cancellation and late settlement stay quiet.
505
839
 
840
+ **The five seconds count on-screen time only (Claude 2.1.310, 2026-10-25).** After
841
+ an accepted folder-trust answer, Claude erases the dialog at once but takes
842
+ seconds to paint its composer. In 30 fresh-folder `autotrust` launches, 10 at a
843
+ time (load average 30–70), the dialog was erased 0.17–0.98 s after it first
844
+ painted. The screen then stayed blank, then showed only the welcome banner
845
+ (logo, version, model, path, one rule). The composer painted 4.0–8.9 s after
846
+ the dialog first appeared. That deadline used to run from the first candidate to
847
+ verified composer clearance, so it timed Claude's boot rather than the gate.
848
+ Every launch whose composer came after 5 s went `blocked` with
849
+ `claude-workspace_trust-prompt` attention at +5.0 s, then `ready` when the
850
+ composer painted: 17 of the 27 launches that reached `ready`. At each of those
851
+ blocks the rendered frame held no trust dialog. Another 100 launches the same
852
+ day show the same split: 51 of 94 `ready` launches blocked; the dialog was
853
+ erased 0.05–1.06 s after first paint; erase to composer took 0.72–8.23 s, median
854
+ 4.6 s. Headless `elwood run` treats that rule id as a human prompt and kills the
855
+ session: 5 of 7 runs, 14 at once in fresh `$TMPDIR` folders, failed with
856
+ `blocked_prompt`, against none with this change.
857
+
858
+ The budget therefore elapses only on frames that show the gate: it is five
859
+ seconds of cumulative visible time. A frame without it pauses the budget, and
860
+ the same gate's return resumes the remainder rather than a fresh five seconds,
861
+ so a gate that flickers still exhausts one budget. Only a gate that stays absent
862
+ from its departure through verified clearance is guaranteed never to block. The
863
+ input hold does not change: it lasts until verified composer or successor
864
+ clearance. No separate post-answer
865
+ bound replaces the old one. Boot time grows with load, so any fixed bound only
866
+ moves the false attention later. A CLI that never paints after an answered gate
867
+ leaves the session held, as before, but no longer reported as a trust prompt.
868
+ Interleaved under the same load, 16 at once, the old rule blocked 10 of 16
869
+ launches and this one 0 of 16, with 10 boots over 5 s in each arm. Sixteen
870
+ launches matter: eight on a quiet machine all booted in under 4 s and never
871
+ reached the condition. The sanitized PTY bytes of the slowest launch are in
872
+ `tests/fixtures/claude-2.1.310/trust-slow-boot/`;
873
+ `tests/claude/session-trust-slow-boot.test.ts` replays them at their recorded
874
+ times. `tests/e2e/trust-slow-boot-claude.e2e.ts` launches 16 fresh folders at
875
+ once against the real CLI and logs how many boots exceeded 5 s. C-ATTN-03,
876
+ C-TRUST-01.
877
+
878
+ **An answer is reported when the composer verifies it, not when the dialog
879
+ leaves (Claude 2.1.310, 2026-10-25).** The blank and banner-only frames between
880
+ the erased folder-trust dialog and the composer show neither the gate nor a
881
+ composer. The attempt read them as its choice vanishing and cancelled itself
882
+ quietly, which is right for a choice lost before its Enter. By then, though, the
883
+ confirming write had fulfilled, so the cancellation discarded a real answer.
884
+ None of the 30 fresh-folder `autotrust` launches above emitted `startup_prompt`,
885
+ although the 27 that reached `ready` had answered their gate. A confirmed
886
+ attempt now waits through dialog-free frames and writes nothing; it re-reads the
887
+ screen only when a new frame renders, so the wait holds no timer. Verified
888
+ composer or successor clearance reports it once. If the same gate returns, the
889
+ attempt answers again within the remaining on-screen budget. Expiry, a changed
890
+ gate, a complete off-allowlist gate, or closing cancels it without a report. With this change, all 21 launches of the two Claude trust e2e
891
+ files (5 at once, then 16 at once with every composer more than 7.5 s after the
892
+ prompt) reported exactly one `startup_prompt`, `sent home+down+enter`. Swapping
893
+ in the previous trust code failed both files: no launch reported its answer.
894
+ Codex 0.164 shows its directory-trust prompt in a fresh git folder, and its
895
+ answers were lost the same way: in four rounds of 4 such launches at once, the
896
+ previous code reported 0 of 16 answers and this change 16 of 16, each `sent 1`.
897
+ `tests/claude/session-trust-slow-boot.test.ts` replays the recorded launch and
898
+ requires exactly one report, and both Claude trust e2e files require one
899
+ `workspace_trust` `startup_prompt` per launch that names a write the launch made. C-CLAUDE-10,
900
+ C-TRUST-01.
901
+
506
902
  Both CLIs reword gates often, so an off-allowlist gate behind a recognized header
507
903
  prefix fails closed: a header-shaped region (`headerStart` in
508
904
  `src/core/trust/dialog.ts`) with real header text, selectable options, only
@@ -523,6 +919,128 @@ A no-model Codex resize probe at 100×12, 100×8 and 100×6 confirms that the we
523
919
  box can scroll away while its composer and model/reasoning/path footer remain.
524
920
  Those native rows also prove clearance; welcome presence is not required.
525
921
 
922
+ ### Codex numbered trust retries (0.164, 2026-10-25)
923
+
924
+ Under `autotrust`, Codex 0.164 sometimes received "1" as a user message right
925
+ after its directory-trust gate. Elwood wrote `1\r` when the `Folder access` gate
926
+ painted, then wrote `1\r` again 250 ms later because the frame still showed the
927
+ gate. Under load Codex had taken the first answer but painted its composer after
928
+ the retry, so the second `1\r` submitted "1" as a turn. In one capture the writes
929
+ landed at 21.26 s and 21.52 s, and the transcript then showed `› 1` with the turn
930
+ running at 24.7 s.
931
+
932
+ Codex also discards keys written as the gate paints. In 8 fresh git folders
933
+ launched at once (load average about 140), with `autotrust` off and a probe
934
+ typing `1\r` 8–33 ms after the gate's first frame, 7 of the 8 answers were
935
+ dropped. No output followed for 3 s. A bare `\r` then confirmed the
936
+ highlighted `› 1. Trust and continue` 24–156 ms later in all 7. The eighth
937
+ answer (written 13 ms after the gate's first frame) was taken. Once each
938
+ composer painted, a bare `\r` on the empty composer submitted nothing in 8 of
939
+ 8: no hook fired, no `Working` row, and the status stayed `ready`. Typing `1`
940
+ alone selects option 1 without confirming it (the gate stayed for 40 s). So
941
+ neither a time limit nor new output can tell a dropped answer from one Codex
942
+ is still processing, and a dropped answer produces no output to wait for.
943
+
944
+ Elwood therefore types the number in its first write to a gate, and never
945
+ again on a stale frame alone. While the
946
+ rendered caret is on the affirmative, a Codex retry is Enter alone, every
947
+ 250 ms within the gate's five on-screen seconds. On the gate it confirms the
948
+ highlighted affirmative; on the empty composer it does nothing. Claude never
949
+ gets a bare Enter (its list remounts with the caret on the decline; see the
950
+ next section), and Codex's hook gate opens with its caret on `Review hooks`.
951
+ Those numbered gates are answered again only after a frame rendered from
952
+ output that arrived after the last write still shows them. Once Elwood has
953
+ started writing the number to a gate, every later attempt at it retries this
954
+ way, even when that write or a retry was rejected, since a rejected write may
955
+ still have reached the CLI. Only another complete dialog replacing the gate
956
+ makes its return a fresh gate that gets the number.
957
+ `tests/codex/session-trust-slow-answer.test.ts` covers both cases.
958
+
959
+ Measured with 8 fresh git folders launched at once per round and two rounds per
960
+ arm, at load average 20–60 (leaked shell processes were also slowing the
961
+ machine): on the previous code, 9 of 16 launches typed `1` into the composer (2
962
+ to 11 `1\r` writes each), and only 5 of 16 reported their answer. With this
963
+ change, 0 of 16 did: each typed the number once, then 1 to 15 bare Enters, and
964
+ all 16 reported `sent 1`. `tests/e2e/trust-retry-codex.e2e.ts` makes the lag
965
+ certain by holding Codex's output from Elwood for 2 s after the answer while
966
+ Codex runs normally. On the previous code it failed both runs: seven more `1\r`
967
+ writes reached the composer, "11" was submitted, and the model answered "What
968
+ would you like me to do with 11?". With this change it passed 3 of 3. Freezing
969
+ Codex itself (SIGSTOP for 2 s right after the answer) does not work as a test:
970
+ Codex then exits with `timed out discarding buffered terminal input`, whatever
971
+ Elwood types. Codex 0.164 discards buffered input at screen changes like this
972
+ one, and a stalled discard is fatal.
973
+
974
+ ### Claude trust selection reset (2.1.310, 2026-10-25)
975
+
976
+ Under `autotrust`, 7 of 60 fresh-folder launches of Claude Code 2.1.310 exited
977
+ with code 1 at the folder-trust gate on a loaded machine (load average 60–160 on
978
+ 16 cores, three to six launches at once). Every failure had the same shape. Elwood
979
+ wrote `\x1b[B` (twice in two runs, the second a 250 ms retry), Claude painted
980
+ `❯ Yes, I trust this folder`, and Elwood wrote a bare `\r`. Claude then repainted
981
+ `❯ No, exit` without any key from Elwood and ran its exit sequence
982
+ (`\x1b[?1006l…\x1b[?25h`). In two runs the repaint and the exit sequence arrived in
983
+ one output chunk.
984
+
985
+ The 2.1.310 binary explains it. `TrustDialog` renders its confirm list with
986
+ `cancelFirst`, `focus: "cancel"` and a React key of `"held"` or `"settled"`. The key
987
+ flips to `"settled"` 150 ms after the dialog mounts. An Enter within 150 ms of mount,
988
+ or of an earlier refused Enter, is refused, and the refusal flips the key back to
989
+ `"held"` for another 150 ms. Each key change remounts the list with the cursor on
990
+ "No, exit". On a busy machine the settle timer is already overdue when Claude next
991
+ reads stdin, so it runs first. The Enter that Elwood decided from the `❯ Yes` frame
992
+ is then processed on the remounted list and confirms "No, exit". A probe that sent
993
+ only Down at first paint saw `❯ Yes` and then `❯ No, exit` 90 ms later, with no
994
+ Enter. A refused Enter only resets the cursor: that is the "first Enter is lost"
995
+ pattern in successful runs (Down, Enter, reset, Down, Enter). Separately, Down on
996
+ the last row wraps to the first. In one run a stalled first arrow and its 250 ms
997
+ retry both landed, painting `❯ Yes` and then `❯ No, exit`; an Enter decided between
998
+ them would also confirm "No, exit". Every installed binary from 2.1.268 to 2.1.310
999
+ carries the same remount key on this dialog (the 150 ms window was read from 2.1.305
1000
+ and 2.1.310), and the MCP-server dialog uses it too.
1001
+
1002
+ Claude's list binds `home` to `select:first` and `end` to `select:last` in every
1003
+ installed binary from 2.1.268 to 2.1.310. A live 2.1.310 probe confirmed that Home
1004
+ (`\x1b[H`) and End (`\x1b[F`, `\x1b[4~`, `\x1bOF`) move the trust cursor absolutely
1005
+ and repeat harmlessly. Written as one chunk, `\x1b[H\x1b[B\r` confirmed "Yes" in
1006
+ every probe and every fixed launch, including launches where the remount ran just
1007
+ before the chunk was read.
1008
+ The list's key handlers read focus through a synchronous getter, so one chunk's keys
1009
+ see each other's effect. Inside the 150 ms windows the chunk is refused, which is
1010
+ harmless, and the next retry succeeds. Elwood therefore writes Home plus one Down
1011
+ per row above the affirmative while the rendered cursor is elsewhere. Once the
1012
+ cursor is on the affirmative, it writes that selection and Enter as one write.
1013
+ Relative arrows and bare Enter are never sent. With that rule, interleaved with the
1014
+ failing runs under the same load, 0 of 51 launches exited. In 7 of them Claude
1015
+ repainted `❯ No, exit` after the combined write and still accepted it; with a bare
1016
+ Enter that interleaving exits. `tests/unit/trust-claude-selection-reset.test.ts`
1017
+ replays the real 100×30 frames (`tests/fixtures/claude-2.1.310/folder-trust-*.txt`)
1018
+ against a model of this dialog. `tests/e2e/trust-prompt-claude-concurrent.e2e.ts`
1019
+ launches five fresh folders at once and checks every trust write at the PTY
1020
+ boundary.
1021
+
1022
+ ### Codex takes a trust answer before Elwood's frame shows it (0.164, 2026-10-25)
1023
+
1024
+ Elwood decides each startup retry from the frame it has rendered, and under load that
1025
+ frame trails Codex. Codex 0.164 can take the trust answer and draw its composer while
1026
+ Elwood's frame still shows the folder-access gate. A retry decided from that stale
1027
+ frame lands in the composer. Keys a caller types in that window also reach the
1028
+ composer, so a `1\r` retry written after them would add a digit to the caller's draft
1029
+ and submit it as a turn.
1030
+
1031
+ `tests/e2e/trust-caller-input-codex.e2e.ts` makes the window certain. It holds Codex's
1032
+ output from Elwood for 4 s after the first `1\r`, and types `hello` as soon as Codex's
1033
+ raw output draws its configured composer (the footer naming the session's model). On
1034
+ `main` at 6e396099, at load average 34, Elwood kept retrying the stale gate every
1035
+ 250 ms: 14 more `1\r` writes followed `hello`. Raw caller input now hands the startup
1036
+ prompt on screen to the caller before its keys are written (C-TRUST-01). In two runs
1037
+ with that handoff, nothing followed `hello`.
1038
+
1039
+ The Codex update skip has the same ordering. A `2` decided from a stale update prompt
1040
+ would reach whatever Codex drew next; on the trust gate, `2` is Quit. Caller input
1041
+ ends the skip, and the update stays the caller's until a native composer clears it,
1042
+ because Elwood's frame can lose and regain a stale prompt in between (C-CODEX-12).
1043
+
526
1044
  ### Native Codex startup warnings
527
1045
 
528
1046
  Codex 0.154.0 normally collapses MCP failures to
@@ -533,6 +1051,22 @@ does not send Ctrl+T automatically. Captures and the local failing-server/401
533
1051
  real-CLI test verify the detailed rows. The current welcome chrome and native
534
1052
  warning prefixes distinguish these banners from conversational quotations.
535
1053
 
1054
+ **Codex 0.164.0 shows only a badge (2026-10-25).** In C-E2E-09's native run (an MCP
1055
+ server answering 401, `startup_timeout_sec = 2`), the welcome is no longer boxed, and
1056
+ the only startup trace is `⚠ 1 warning · f2 to view` at the end of the composer's
1057
+ model footer, which the composer clearance already strips. Ctrl+T opened the
1058
+ `/ T R A N S C R I P T /` overlay. It holds both canonical rows, the `elwood_probe`
1059
+ not-logged-in row with its `codex mcp login elwood_probe` command and
1060
+ `⚠ MCP startup incomplete (failed: elwood_probe)`, under an unboxed
1061
+ `>_ OpenAI Codex (v0.164.0)` and above the overlay's own key hints. That overlay is
1062
+ Codex's transcript view, where the conversation also renders, so it is not a startup
1063
+ region; the parser finds no welcome box there and returns nothing. Elwood sends neither F2 nor Ctrl+T, so on 0.164 a
1064
+ session emits no MCP startup warning: the count names no server and no recovery
1065
+ command (PRD §5.7, C-CODEX-09). The captures are
1066
+ `tests/fixtures/codex-0.164.0/boot/warning-badge.txt` and
1067
+ `warning-transcript-overlay.txt`; C-E2E-09 asserts the badge, that its frame reads as
1068
+ the composer, and that no warning parses from it.
1069
+
536
1070
  Native 0.154.0 startup renders no `•`/`●` row at any point: recorded frame by frame
537
1071
  (2026-09-17) through the directory gate, hooks gate, welcome, and composer with
538
1072
  both an MCP endpoint that fails at once and one that accepts the connection and
@@ -559,13 +1093,39 @@ The acceptance is persisted in Claude's global config as
559
1093
  `bypassPermissionsModeAccepted` (the binary describes it as "Whether the user has
560
1094
  accepted the bypass permissions mode dialog"), and `--bg` refuses bypass mode
561
1095
  until it has been accepted once interactively, so the dialog is at most
562
- once-per-machine. **Empirically, on 2.1.268 (Claude Max account, macOS) the
563
- dialog did NOT render at all** — neither through Elwood's `--permission-mode
564
- bypassPermissions` launch nor with a raw `--dangerously-skip-permissions`
565
- launch — and no `bypassPermissionsModeAccepted` key existed anywhere under
566
- `~/.claude*`; the session went straight to the composer with the persistent
567
- `⏵⏵ bypass permissions on (shift+tab to cycle)` footer. Whether it appears is
568
- therefore version/account-gated, and Elwood must be correct in BOTH states:
1096
+ once-per-machine. On 2.1.268 (Claude Max account, macOS) the dialog did not render
1097
+ at all, through Elwood's `--permission-mode bypassPermissions` launch or a raw
1098
+ `--dangerously-skip-permissions` one, and no `bypassPermissionsModeAccepted` key
1099
+ existed anywhere under `~/.claude*`. The likely reason is a user setting, not the
1100
+ version or account: the developer's `~/.claude/settings.json` sets
1101
+ `"skipDangerousModePermissionPrompt": true`. On 2.1.310, in a config that has never
1102
+ accepted the dialog, that setting alone decides whether it renders: in an empty
1103
+ `CLAUDE_CONFIG_DIR` (no settings, never accepted) the dialog renders, and adding the
1104
+ setting there sends the session straight to the composer. The 2.1.310 copy
1105
+ is longer and opens with the cursor on the decline
1106
+ (`tests/fixtures/claude-2.1.310/bypass-permissions-{100,200}.txt`):
1107
+
1108
+ ```
1109
+ WARNING: Claude Code running in Bypass Permissions mode
1110
+
1111
+ In Bypass Permissions mode, Claude Code will not ask for your approval before running potentially dangerous commands.
1112
+ This mode should only be used in a sandboxed container/VM that has restricted internet access and can easily be restored if damaged.
1113
+
1114
+ By proceeding, you accept all responsibility for actions taken while running in Bypass Permissions mode.
1115
+
1116
+ https://code.claude.com/docs/en/security
1117
+
1118
+ ❯ No, exit
1119
+ Yes, I accept
1120
+
1121
+ Enter to confirm · Esc to cancel
1122
+ ```
1123
+
1124
+ Until the native-copy pattern accepted the sandbox sentence and the link, Elwood
1125
+ held this dialog as unrecognized prose and the session stayed `blocked` under
1126
+ `autotrust`. The suite never saw it because the developer's setting hid it, so
1127
+ whether it appears depends on the user's settings and Elwood must be correct in
1128
+ BOTH states:
569
1129
 
570
1130
  - The dialog is an allowlisted trust prompt (`bypass_permissions`), answered
571
1131
  under `autotrust` by the same header-anchored, numbered-or-cursor machinery as
@@ -598,6 +1158,40 @@ prompt; `--no-trust` and generic permission dialogs remain human blocks. The sta
598
1158
  deliver this attention after the responder has already continued, so treating
599
1159
  every attention event as fatal produces a false `blocked_prompt`.
600
1160
 
1161
+ ### Claude keeps the prompt's caret after a declined permission dialog (2.1.310, 2026-10-26)
1162
+
1163
+ A real Claude 2.1.310 session in `default` mode (189x48, Opus 5.5, a private
1164
+ `CLAUDE_CONFIG_DIR` with no user settings) asked to create a file rendered the
1165
+ `Do you want to proceed?` dialog with its cursor hidden. Escape declined it. The next
1166
+ frame showed `⎿ Interrupted · What should Claude do instead?`, the idle composer
1167
+ fenced by rules, the `⏸ manual mode on · ? for shortcuts` footer, a visible cursor at
1168
+ column 2, and the idle title `✳`. The transcript above it still held the submitted
1169
+ prompt, echoed as `❯ Create a file named …`. Claude echoes every submitted prompt that
1170
+ way, so the whole-frame clearance, which rejects any caret row that is not an empty
1171
+ composer, rejected the frame, and the session stayed `blocked` while Claude sat idle:
1172
+ 15 s in the capture, and the whole wait of a C-ATTN-01 run in the same private config.
1173
+ Attention clearance now also accepts the last composer with its rules and native
1174
+ footer (`claudeLastComposerClearance`), the path the model picker already used for
1175
+ `❯ /model` (`tests/fixtures/claude-2.1.310/permission-declined/`). The real-CLI C-ATTN-01 runs
1176
+ Claude with the developer's own config dir, and had not caught this on the developer's
1177
+ machine: their own `PermissionRequest` hook allowed the command, so the dialog never
1178
+ rendered there. This capture is a manual reproduction in a private config dir with no
1179
+ user hooks. C-ATTN-02.
1180
+
1181
+ A resumed session repaints those carets too. In the same private config, a `default`-mode
1182
+ session ran one turn and was stopped, then resumed with `highTrust`. Bypass mode had
1183
+ never been accepted there, so the resume opened on the bypass-permissions dialog, and
1184
+ autotrust answered it. Claude then painted the resumed transcript, with
1185
+ `❯ Reply with exactly: hello. …`, above the idle bypass-mode composer, its footer, and a
1186
+ visible cursor at column 2. Startup trust clearance used the same whole-frame check,
1187
+ so the answer never settled. The session sat at `running` for the 60 s observed and
1188
+ never reached `ready`. Startup trust clearance now uses the same predicate as attention
1189
+ clearance (`tests/fixtures/claude-2.1.310/resume-bypass/`). Rerun with that change, the same
1190
+ resume answered the dialog, recorded a `bypass_permissions` startup prompt, and reached
1191
+ `ready` 4.6 s after launch. A folder-trust gate on
1192
+ resume goes through the same clearance, but that case was not captured. C-TRUST-01,
1193
+ C-CLAUDE-21.
1194
+
601
1195
  ## Codex in-TUI update prompt (and the restart loop)
602
1196
 
603
1197
  Independent of the `autoupdate` preflight (`codex update` run before spawn), the
@@ -703,6 +1297,241 @@ other errors, pre-exit errors, and persistent `EPERM` still fail. Controlled
703
1297
  shutdown also leaves the reap to its owning termination path instead of emitting
704
1298
  a competing best-effort warning. Verified against Codex 0.153.3 on macOS.
705
1299
 
1300
+ ## Hook and MCP process groups (2026-10-25)
1301
+
1302
+ Claude Code 2.1.310 and Codex 0.164.0 start every hook command with `setsid()`.
1303
+ Measured on macOS with `ps -o pid,ppid,pgid,command` and `os.getsid()` while a
1304
+ slow hook was in flight under a real Elwood session:
1305
+
1306
+ - Claude runs a hook as a direct child of the CLI (`ppid` = the PTY leader) whose
1307
+ `pgid` and `sid` both equal its own pid. A compound command runs as
1308
+ `/bin/sh -c '<command>'`; a plain one such as Elwood's
1309
+ `'<node>' '<bridge>.mjs'` is executed directly. An Electron host's
1310
+ `ELECTRON_RUN_AS_NODE=1 …` command needs the shell, consistent with the
1311
+ reported orphan `/bin/sh -c node hook-bridge.mjs`.
1312
+ - Codex runs a hook as `/bin/zsh -c '<command>'` (the user's shell), also with its
1313
+ own `pgid` and `sid`. Codex started an MCP server (`node ./scripts/start-mcp.mjs`,
1314
+ from a plugin it installed in the sandboxed `CODEX_HOME`) in its own process
1315
+ group but inside the leader's session.
1316
+ - The children the CLI starts without detaching, such as Claude's IDE probe
1317
+ (`ps aux | grep …`), stay in the leader's group.
1318
+
1319
+ So the leader-group SIGKILL (`kill(-leader)`) cannot reach a hook. Once the
1320
+ leader dies the hook is reparented to PID 1 and nothing ties it to the session;
1321
+ only the parent-pid chain links a hook to its leader, and only while the leader
1322
+ lives. With the group reap alone, `teardown()` during a `SessionStart` project hook
1323
+ running `sleep 45` resolved in 25 ms and left that `/bin/sh` and its `sleep` alive
1324
+ under PID 1. `stop()` while a `SessionEnd` hook ran `sleep 46` timed out after
1325
+ 5 s; Claude waits for `SessionEnd` hooks on SIGTERM, so Elwood escalated to SIGKILL
1326
+ and left the hook running. On Codex, `teardown()` during a `UserPromptSubmit`
1327
+ config hook left the `zsh`/`sleep` group alive, and the MCP server group outlived
1328
+ the CLI briefly. Elwood's own bridge processes died only because teardown then
1329
+ closes the bridge socket; the reap itself never reached them.
1330
+
1331
+ When Claude exits on SIGTERM within the graceful window it kills its in-flight
1332
+ hooks itself: a `stop()` during the same `sleep 45` hook exited in about 1 s with
1333
+ no survivors. On macOS, `killpg` on a group whose members are all unreaped zombies
1334
+ returns `EPERM`, not `ESRCH`. `src/runtime/shutdown/process-table.ts` reads the
1335
+ table and finds these groups by walking parent pids from the leader.
1336
+
1337
+ Elwood therefore walks the tree only before a SIGKILL it sends to a live leader. It
1338
+ SIGSTOPs the leader's group and lists
1339
+ `/bin/ps -A -o pid=,ppid=,pgid=,stat=,lstart=,ucomm=,pid=` (the start time tells a reused
1340
+ pid from the process that had it; the short name and the pid again are for Codex's
1341
+ daemon, below). A
1342
+ group id from `ps` is only a snapshot, `kill()` returning means the SIGSTOP was sent,
1343
+ not delivered, and `ps` reads each process's state at its own moment (on macOS it
1344
+ takes the set of pids first and each row's state afterwards). So Elwood relies only
1345
+ on a process that read stopped (`stat` starting with `T`), inside a group it stopped,
1346
+ in two consecutive listings. A group that holds a live descendant is stopped once
1347
+ such a process is a parent of one of its members: that parent cannot reap the
1348
+ member, so the group keeps its id until the signal. The walk succeeds only on a
1349
+ listing that shows the leader live, leaves no live group unreached, and holds every
1350
+ live process in every stopped group that way, the leader's included, so none of them
1351
+ can have started a process the listing missed; a leader exit observed first fails it,
1352
+ because the leader's detached children are then out of reach. Then the stopped groups
1353
+ are SIGKILLed deepest first, except one that holds Codex's daemon, which is resumed
1354
+ (below) (`src/runtime/shutdown/detached-sweep/`). That is two listings with no hook in
1355
+ flight, and two more per level of nesting. A process whose own parent exited before
1356
+ the walk is already under PID 1 and out of reach. macOS refuses a stop or a kill of
1357
+ a group whose members are all unreaped zombies with `EPERM`, so either counts as a
1358
+ failure only if a later listing still shows a live member of that group.
1359
+
1360
+ `killpg` is not atomic against `fork` (measured 2026-10-25, Claude 2.1.310, macOS
1361
+ Darwin 25.5): a child forked while the group's SIGSTOP goes out can miss it and keep
1362
+ running. The PTY leader is the user's interactive login shell (§4.2), and in one
1363
+ Claude `teardown()` out of nine, with no hook in flight, `claude` had already exited
1364
+ and the shell was running a `$(… | tr …)` pipeline from the user's shell
1365
+ configuration. One subshell of it read `R` on every listing for the whole
1366
+ three-second deadline while the rest of the group read `T`, so the leader's group was
1367
+ never held and `teardown()` rejected. A zsh group forking `$(echo a | tr a b)` in a
1368
+ loop, SIGSTOPped once, left one member running in 1 of 150 stops, and a second
1369
+ SIGSTOP stopped it every time. So any listing that shows a live process in a stopped
1370
+ group not stopped sends that group SIGSTOP again: the leader's only while its exit
1371
+ has not been observed, a descendant group because its held parent keeps the id. A
1372
+ listing cannot tell a stop still in flight from a missed one, and a second SIGSTOP
1373
+ to a stopped process does nothing, so the walk does not try to. With the re-stop, 40
1374
+ of 40 real no-hook teardowns completed, 5 of them sending the leader's group a
1375
+ second SIGSTOP; without it, 1 of 9 had rejected.
1376
+
1377
+ The walk's cost, measured 2026-10-25 on Claude 2.1.310 on a shared machine: one `ps`
1378
+ listing of about 1,800 rows took 110–240 ms at load 33–35. At load 19–26, ten
1379
+ `teardown()`s with no hook in flight took 81–291 ms (median 227, two listings), and
1380
+ ten with a `SessionStart` hook in flight 175–567 ms (median 373, four), against 2–3
1381
+ ms and 7–18 ms with the walk disabled, where the in-flight hook survived every time. `tests/e2e/teardown-reap.e2e.ts` drives the
1382
+ real CLIs: Claude `teardown()` with a hook in its own group, a `stop()` whose
1383
+ `SessionEnd` hook outlasts the graceful window, and Codex `teardown()` with a hook.
1384
+ With the walk disabled all three fail with the hook still running.
1385
+
1386
+ On a loaded host the walk outgrew its first three-second deadline (measured
1387
+ 2026-10-26, macOS Darwin 25.5, about 3,700 processes on the host). A `kill()` of a
1388
+ setsid hook with a setsid child of its own takes six reads. That is two to hold the
1389
+ leader's group, and two more for each level of nesting. At a 1-minute load of 70 to
1390
+ 92, those reads took 78–644 ms each, and the walk took 1.1–2.6 s. Under the
1391
+ repository's own parallel test run on that host, reads passed half a second, and so
1392
+ six of them passed three seconds. `kill()` then rejected with `termination_failed`,
1393
+ though each read was making progress. The deadline is now ten seconds.
1394
+ `tests/unit/sweep-deadline-real.test.ts` slows each real read by 600 ms and checks
1395
+ that `kill()` still reaps such a tree.
1396
+
1397
+ Codex also starts a shell of its own, not a hook, that the walk has to reach
1398
+ (measured 2026-10-25, Codex 0.164, macOS Darwin 25.5). A few seconds after launch
1399
+ (2.1–2.6 s after `startCodex` was called, in these runs) it snapshots the user's
1400
+ shell environment with `/bin/zsh -lc '<script>'`. The script sources
1401
+ `$ZDOTDIR/.zshrc`, else `$HOME/.zshrc`, then prints the environment. The shell is a
1402
+ session leader of its own (`stat` `Ss`, `pgid` equal to its pid) whose parent is the
1403
+ `codex` leader. With a normal profile it lived about 0.8 s. A `.zshrc` that sleeps
1404
+ only when non-interactive (`[[ -o interactive ]] || sleep 120`) holds it open without
1405
+ slowing the PTY's own interactive login shell, and with that profile:
1406
+
1407
+ - `teardown()` with the group reap alone left the `zsh -lc` and its `sleep` running
1408
+ under PID 1. With the walk, `teardown()` and `kill()` reaped both.
1409
+ - `stop()`'s SIGTERM: Codex exited in about 190 ms. The leader exited within the
1410
+ graceful window, so there was no walk, and both the `zsh -lc` (now under PID 1)
1411
+ and its `sleep` survived.
1412
+ - A human at the terminal, `/quit` then Enter, or Ctrl-C twice: Codex exited about
1413
+ 6 s after the keys. The `zsh -lc` was gone, but the profile's `sleep` survived
1414
+ under PID 1.
1415
+
1416
+ Claude 2.1.310 under the same profile started no shell that sources `.zshrc`
1417
+ non-interactively, and nothing survived. So the premise behind the graceful-stop
1418
+ exception, that the CLI's own SIGTERM shutdown ends its detached descendants, holds
1419
+ for Claude's hooks but not for this Codex shell: a `stop()` during the snapshot
1420
+ leaves it running. `tests/e2e/codex-snapshot-reap.e2e.ts` holds a snapshot open
1421
+ this way and checks that `teardown()` reaps it.
1422
+
1423
+ ## Codex 0.164 starts an app-server daemon per `CODEX_HOME` (2026-10-25)
1424
+
1425
+ Codex 0.158.0 and later list the feature `daemon_auto_start` as stable and on (0.156.1
1426
+ lists it as experimental and off; 0.155.1 does not have it). With it, the Codex 0.164.0
1427
+ TUI can start a managed app-server daemon for its `CODEX_HOME`. Measured on macOS
1428
+ (Darwin 25.5) with fresh homes: 11 of 14 launches without the hook-trust bypass started
1429
+ one, the earliest beginning its install 1 s after launch, and none of 5 launches with
1430
+ the bypass did, one of them an Elwood session watched for 17 s. What decides it was not
1431
+ determined. The e2e suite's removed homes had left 29 of these daemons running by that
1432
+ evening.
1433
+
1434
+ - It installs a copy of the CLI, about 260 MB, at
1435
+ `$CODEX_HOME/packages/app-server-daemon/releases/<version>/bin/codex`. One such home
1436
+ measured 453 MB.
1437
+ - It runs `codex app-server --listen unix:// --analytics-default-enabled --managed-daemon`
1438
+ and `codex app-server daemon pid-update-loop`. Each is a session leader of its own and
1439
+ a child of the TUI, reparented to PID 1 when the TUI exits. The control socket is
1440
+ `$CODEX_HOME/app-server-control/app-server-control.sock`, pid files are under
1441
+ `$CODEX_HOME/app-server-daemon/`, and a socket directory and lock are under
1442
+ `/tmp/codex-daemon-<uid>/`. Plugin MCP servers (`node ./scripts/start-mcp.mjs`) run as
1443
+ its children.
1444
+ - The TUI then reads through it as client `codex-tui`. The daemon's rows in
1445
+ `logs_2.sqlite` show `account/read`, `config/read` (the project trust read),
1446
+ `hooks/list`, and `model/list`.
1447
+ - It outlives the TUI. Those 29 daemons had started 14 minutes to 1 h 49 min earlier,
1448
+ their homes removed seconds after, and were all still running, each holding its
1449
+ deleted binary. They refreshed `models_cache.json` and recreated `logs_2.sqlite`,
1450
+ `.sqlite-maintenance.lock`, and the plugin cache in the removed home. A removal that
1451
+ raced those writes failed with `ENOTEMPTY` (C-E2E-09, 1 run of 3).
1452
+ - `codex app-server daemon stop`, run with that `CODEX_HOME`, stopped the daemon and its
1453
+ MCP servers but left the updater loop running. SIGTERM ended both within 2 s, and the
1454
+ daemon stopped its MCP servers and removed its `/tmp/codex-daemon-<uid>/` entries.
1455
+ `codex app-server daemon start` started both on every run, in about 2.5 s.
1456
+
1457
+ Elwood's sessions use the user's own `CODEX_HOME` and always bypass hook trust, so a
1458
+ daemon there is the one the user's own Codex shares, as for a human at the terminal. The
1459
+ e2e suite stops whatever still runs from a sandbox or from its default home before
1460
+ removing it (`tests/e2e/scratch-processes.ts`). That is every process whose command line
1461
+ names a path inside the home, plus its descendants: SIGTERM first, then SIGKILL 3 s
1462
+ later. A sandbox where something will not stop is kept.
1463
+ `tests/e2e/scratch-processes.e2e.ts` starts a daemon with `daemon start` and checks that
1464
+ removing the sandbox, and exiting the run, stop it.
1465
+
1466
+ ## Elwood's teardown never kills that daemon (2026-10-26)
1467
+
1468
+ While the TUI that started them runs, the daemon and its updater loop are setsid
1469
+ children of it, the shape of a hook in flight. So when a session's own TUI had started
1470
+ them, the detached walk that `kill()`, `teardown()`, and a `stop()` escalation run killed
1471
+ the daemon, its updater loop, and everything the daemon had started, though the daemon
1472
+ serves every Codex on the home, the user's own included. The walk now never descends into
1473
+ one (PRD §5.3, C-LIFE-10): a process whose `ucomm` (short name) is `codex`, that leads its
1474
+ own group, whose parent is named `codex` or is PID 1, and whose `comm` has
1475
+ `/packages/app-server-daemon/` in it. On macOS `comm` is `argv[0]`, which Codex sets to
1476
+ the full path; it reads that way even after the home is removed.
1477
+
1478
+ `comm` is read, one `ps -o comm= -p <pid>` per process and at most four at once, only for
1479
+ the processes that pass the other tests and that the walk can reach: the session's TUI and
1480
+ its descendants. None starts past the table read's deadline; the read then fails as a timed
1481
+ out one does. Every extra read costs a walk whose deadline, three seconds when these were
1482
+ measured, must hold on a loaded host:
1483
+
1484
+ - `comm` in every table read doubled its cost (94 to 200 ms at a load of about 220): 4 of
1485
+ 9 real-process reap tests failed with `ETIMEDOUT`, none on `origin/main`.
1486
+ - Given several pids, macOS `ps` reads every process on the host (230 to 370 ms at a
1487
+ load of about 25): with one `ps -p` for all candidates, a real Codex `teardown()`
1488
+ failed in 5 of 7 runs, none of 2 on `origin/main`.
1489
+ - One `ps` per candidate anywhere on the host took 4 to 9 ms from a shell, but 120 to
1490
+ 870 ms when spawned by the process tearing the session down. The user's own daemon was
1491
+ always a candidate, so every read paid that again: a real Codex `teardown()` failed in
1492
+ 2 of 5 runs, none of 5 on `origin/main`.
1493
+
1494
+ A walk remembers each daemon it found, by pid, parent, and group, so when the session's
1495
+ TUI started one, the walk's first read pays its path reads, and no later read does unless
1496
+ its parent changed. A candidate no read has confirmed, such as the TUI's fork before its
1497
+ `exec`, is read again on every read until one does or it is gone. A walk with no
1498
+ candidate under its TUI runs the same single `ps` per read as before. In 3
1499
+ real Codex `teardown()`s, alternated with 3 on `origin/main`, it ran no path read. One run
1500
+ on each side failed at the deadline, when the host's 3,700 processes made a single table
1501
+ read take 1.6 to 2.1 s; the other reads took 350 to 540 ms.
1502
+
1503
+ No real-Codex e2e proves this: in 8 runs of Codex 0.164.0 under Elwood, from fresh
1504
+ homes, with and without the hook-trust bypass and the update check, with holds of up to
1505
+ 40 s, none started the daemon. `tests/unit/reap-detached-real.test.ts` builds the
1506
+ measured shape from real processes instead, with its install in place and removed.
1507
+
1508
+ The TUI forks the daemon as a `codex` group leader of its own and then `exec`s it, so a
1509
+ path read can come between the two, and the walk can then stop the group after the
1510
+ `exec`. A stopped process cannot `exec`, so the kill pass goes by the latest read: a group
1511
+ it shows holding the daemon, or a process not stopped that could still become it, gets
1512
+ SIGCONT, not SIGKILL. A leader killed by something else while the walk holds it leaves
1513
+ that stopped daemon under PID 1, out of the leader's reach, so each group the walk stopped
1514
+ is a root of its own for the path reads. `tests/unit/shared-daemon-real.test.ts`
1515
+ reproduces both with a real process: Node cannot `exec`, but setting `process.title`
1516
+ changes what macOS `ps` reads as `comm` (`ucomm` stays the executable's name).
1517
+
1518
+ A descendant can set its `comm` to the daemon's path (Node does by setting
1519
+ `process.title`), and a binary named `codex` that leads its own group is then spared as
1520
+ the daemon would be. That gives it nothing it lacks: the walk follows parent pids from the
1521
+ leader, so a descendant whose parent exits first is out of its reach. Measured on
1522
+ `origin/main` (2026-10-26): a setsid hook that backgrounds `/bin/sleep 300` and exits
1523
+ leaves the sleep under PID 1 before `kill()`, which resolves with the sleep still running.
1524
+
1525
+ ## `ps` prints a process's short name raw (2026-10-26)
1526
+
1527
+ To recognize Codex's daemon (above), each table read also lists every process's short
1528
+ name (`ucomm`), and `ps` prints it raw. A process whose name is `a`, a newline, and
1529
+ `1 5000 1 S ` reads as two rows, the second a forged process 1 whose parent is 5000
1530
+ (measured on macOS 26). `comm` and `ps -c` escape it as `\012`, but `comm` in every table
1531
+ read doubled its cost (94 to 200 ms at a load of about 220). So each row ends with its pid
1532
+ again (`-o pid=,ppid=,pgid=,stat=,lstart=,ucomm=,pid=`), and a read with any line that
1533
+ is not one whole row, or one pid twice, is unreadable: the walk fails rather than act on it.
1534
+
706
1535
  ## Claude "Not logged in" is a distinct re-auth banner from "Login expired"
707
1536
 
708
1537
  Claude's mid-session re-auth banners are NOT one string. Besides the lapsed/revoked
@@ -747,6 +1576,13 @@ new CLI version or native capture was verified.
747
1576
  means observed: each write first awaits `terminal.settled()`, because a dialog can
748
1577
  be received before it is rendered. xterm parses the first write after user input
749
1578
  synchronously and later ones on a timer, so that window is real and input-dependent.
1579
+ - Claude 2.1.310 shows a message submitted during a running turn as queued. It renders
1580
+ as `❯ <message>` above the composer, with `ctrl+x ctrl+s to send now` beneath it, and
1581
+ the composer reads `❯ Press up to edit queued messages`. In a turn told not to use
1582
+ tools it was still queued 10 s later, with the reply still streaming. A check that the
1583
+ message left the composer must read only the last `❯` line, because the queued row
1584
+ also starts with `❯` and holds the message. Observed with `sendGuidance` in a private
1585
+ config dir, 2026-10-26 (C-API-37).
750
1586
  - **Writing a literal ESC byte in source is fragile** — it can be silently
751
1587
  stripped by an editor/pipeline (this once neutered an interrupt e2e). Always
752
1588
  write the escape sequence form (``), never a raw ESC character.
@@ -836,8 +1672,9 @@ Four real-PTY edges were invisible to the unit fixtures:
836
1672
  - Codex 0.153.3 can paint the numbered menu before its input loop accepts the first
837
1673
  hotkey. A successful PTY write therefore does not prove the dialog cleared. The
838
1674
  responder runs one bounded retry operation while a safe numbered option remains
839
- on the current rendered frame; once it disappears, no delayed key can reach the
840
- composer. Some PTY hosts close their input side before process completion and
1675
+ on the current rendered frame. A frame showing the option does not prove the key
1676
+ was ignored, so a retry can still reach the composer; it is Enter alone, or it
1677
+ waits for new output (see "Codex numbered trust retries"). Some PTY hosts close their input side before process completion and
841
1678
  then return `EIO` from `setRawMode(false)`; contain only terminal-gone restoration
842
1679
  errors because that host is already the sole remaining terminal-mode owner.
843
1680
  - Interactive login-shell probes can give their external command control of the
@@ -893,11 +1730,30 @@ five rows in ~6s; real Codex six rows in ~5s. Two environment facts matter:
893
1730
  - When Elwood itself runs nested inside a Claude Code session (`CLAUDECODE` and
894
1731
  `CLAUDE_CODE_*` set), claude >= 2.1.201 skips transcript persistence for the
895
1732
  nested instance; a headless run then fails `wait_timeout` ("transcript did not
896
- catch up after ready") and floods `transcript_read_error (ENOENT)`. The e2e
897
- helper strips those variables; do the same for manual smoke runs.
898
- - Even outside nesting, one `transcript_read_error (ENOENT)` warning appears at
899
- Claude startup before the first turn creates the transcript file. It is
900
- pre-existing run behavior, not specific to `models`.
1733
+ catch up after ready"). The e2e helper strips those variables; do the same
1734
+ for manual smoke runs.
1735
+ - **Claude 2.1.310 creates the transcript file only when the first turn is
1736
+ submitted (2026-10-25).** `SessionStart` already carries `transcript_path`, but
1737
+ the file was still absent 4 s after `ready` in every probe and appeared about
1738
+ 1 s after the first prompt. Elwood used to report each 500 ms poll of that
1739
+ missing file as `transcript_read_error (ENOENT)`: 12–13 warnings per session
1740
+ with a 4 s idle, and a flood when nested persistence never creates the file.
1741
+ The Claude cursor now treats ENOENT as "not created yet" until a stat has
1742
+ seen the file. ENOENT after that, and any other read error, still warns.
1743
+ Codex never warned here: its shared cursor's `fileSize` already maps ENOENT
1744
+ to an empty file. C-CLAUDE-24.
1745
+ - **That same mapping hid a removed Codex transcript (2026-10-25).** On Codex
1746
+ 0.164.0, `SessionStart` arrives only with the first submission, and its
1747
+ `transcript_path` is a realpath (`/private/var/…/sessions/YYYY/MM/DD/rollout-…jsonl`
1748
+ under `CODEX_HOME`); the file exists by the time that turn's `task_complete` is
1749
+ committed. Deleting it afterwards made the next poll read size 0, which the
1750
+ cursor took for a truncation, so nothing warned and the session's later
1751
+ transcript activity was silently gone: no `transcript_read_error` within 10 s
1752
+ on `main`. The Codex scan now reads through the shared cursor's existence
1753
+ policy, as Claude's does: until a stat has seen the file, ENOENT waits silently;
1754
+ after that, every poll, and the final flush, warns with `ENOENT` while the file is
1755
+ missing. A file that reappears is read with the
1756
+ existing truncation rule. C-CODEX-23.
901
1757
 
902
1758
  `elwood sessions` reports `live` from the presence of a `.sock` file in the
903
1759
  session's stable socket home. A connect probe was rejected on purpose: the
@@ -905,6 +1761,72 @@ bridge treats any connection that closes without a frame as a malformed request
905
1761
  and emits `hookError`/`activity` on the live owner, so presence is the only
906
1762
  side-effect-free signal. C-CLI-21 through C-CLI-24.
907
1763
 
1764
+ ## Probe shell startup under concurrency (2026-10-25)
1765
+
1766
+ Preflight probes run through the user's interactive login shell (§4.2), so their wall
1767
+ time is the user's profile plus the CLI. On a 16-CPU host already at load 35–90 with a
1768
+ typical developer profile (oh-my-zsh with seven plugins, `nvm.sh`, `fnm env`,
1769
+ `brew shellenv`), measured with Claude Code 2.1.310:
1770
+
1771
+ - `claude --version` without a shell took 0.01–0.12 s at 1, 4, 8 and 16 concurrent
1772
+ runs. There is no `~/.claude.json` lock, autoupdate check or keychain wait on this
1773
+ path. An empty `CLAUDE_CONFIG_DIR` changed nothing, and `zsh -l -i -c true` costs
1774
+ the same as the real probe.
1775
+ - The interactive login shell alone took 2.3–7.6 s for one probe, about 2 s each at 4
1776
+ concurrent, about 5 s at 8, 7–19 s at 16 (p50 8–18 s), and 15.5–30.9 s at 32. A
1777
+ trace of one startup put about 2.5 s in `nvm.sh`'s command substitutions and 1.2 s
1778
+ in `compinit`/`compdef`. Fresh or shared working directories made no difference, so
1779
+ this is CPU saturation from fork-heavy startup files, not a shared lock.
1780
+ - With an empty private `ZDOTDIR`, the same probe took 0.03–0.23 s at 16.
1781
+
1782
+ The original 15 s bound covered both phases. 16 concurrent `elwood run`s therefore
1783
+ failed `claude_start_failed` ("`claude --version` failed", `ETIMEDOUT`) at 17.5–18.9 s:
1784
+ 16 of 16 in one of three fleets at load 68, and 14 of 16 in the original report. The
1785
+ probe shell now writes `elwood-probe-command-start` on descriptor 3, a fourth pipe the
1786
+ runner opens, just before the command, then closes it so the command does not inherit it.
1787
+ Writing it to stderr was the first version; a review pointed out that a profile running
1788
+ `exec 2>&1` or `exec 2>/dev/null` would hide it. Real zsh with this host's profile, and
1789
+ zsh with either redirect in a private `ZDOTDIR`, delivered the line on descriptor 3. The
1790
+ bash check ran `bash --rcfile … -i`, a non-login shell, which keeps descriptor 3 too; macOS's
1791
+ bash as an interactive login shell does not (next section).
1792
+ The profile gets a 60 s launch bound and the CLI keeps 15 s from that line (C-PERF-05).
1793
+ With the stderr version, three 16-run fleets at load 48–74 passed preflight 48 of 48;
1794
+ the bounds are the same on descriptor 3. The real-shell e2e
1795
+ uses a private `ZDOTDIR` whose `.zshrc` sleeps 4 s with a 2 s command bound; `main`
1796
+ fails it with that same `ETIMEDOUT`.
1797
+
1798
+ ## Probe start line under macOS bash 3.2 (2026-10-25)
1799
+
1800
+ macOS's `/bin/bash` 3.2.57, and `/bin/sh`, which is the same bash in sh mode, close every
1801
+ inherited descriptor above stderr when started with both `-l` and `-i`, even with
1802
+ `--noprofile --norc`. Pipes a Node parent opened on 3, 9, 10 and 19 were all closed before
1803
+ the `-c` script ran. `-l` alone or `-i` alone keeps them, and so does Linux's bash 5.2.15
1804
+ (Debian, in the `node:24` image) as an interactive login shell. zsh 5.9 and ksh 93u+ keep
1805
+ them; dash keeps 3–9 but cannot name a descriptor of 10 or more, so a higher number is no
1806
+ fix. With descriptor 3 as the only channel, every probe through those shells failed `login
1807
+ shell exited with status 0 before starting the probe command` although its command ran,
1808
+ and so did `elwood`'s agent detection. Any macOS user whose login shell is bash could not
1809
+ start a session. GitHub's `macos-latest` runner user's shell is bash.
1810
+
1811
+ The probe script now tests the write, then keeps descriptor 3 open through the e2e home
1812
+ guard (PRD §12), whose stop line takes the same path, and closes it only before the command:
1813
+ `{ echo L >&3; } 2>/dev/null || echo L; <guard>; exec 3>&-; <command>`. zsh, ksh, macOS
1814
+ and Debian dash, non-login bash 3.2 and bash 5.2 took descriptor 3 when it was open and
1815
+ stdout when it was closed, and none printed the failed redirection's error. The fallback
1816
+ is stdout, not stderr. Under real `bash -l -i` with a
1817
+ `~/.bash_profile` of `exec 2>/dev/null` or `exec 2>&1`, the line arrived on stdout and the
1818
+ probe passed. A stderr fallback would have been discarded by the first and moved into
1819
+ stdout by the second. A profile that hid stdout would hide the command's own output and
1820
+ fail the probe regardless. The runner removes the line from captured stdout. A profile
1821
+ that runs `exec 3>&-` itself now falls back too. Only a profile that reopens descriptor 3
1822
+ onto something else still loses the line. Because stdout is now watched for every probe,
1823
+ the line ends in a random per-process token: a profile that printed the bare
1824
+ `elwood-probe-command-start` would otherwise start the command's bound while the profile
1825
+ was still loading. `tests/unit/probe-closed-descriptor.test.ts` covers this with a
1826
+ `/bin/sh` stand-in that closes descriptor 3 on every host, and with real `/bin/bash -l -i`
1827
+ where `/bin/bash` is 3.x. The nine tests of its first version all fail on `main` at
1828
+ 613b3ea5.
1829
+
908
1830
  ## Testing against the real CLIs
909
1831
 
910
1832
  - `test:e2e` runs **serially** (`--test-concurrency=1`): Codex config-file
@@ -916,6 +1838,115 @@ side-effect-free signal. C-CLI-21 through C-CLI-24.
916
1838
  CLI SHOULD have a real-CLI e2e. Every fact in this file is one a unit test could
917
1839
  not have caught.
918
1840
 
1841
+ ### Claude under a private `CLAUDE_CONFIG_DIR` (2.1.310, 2026-10-26)
1842
+
1843
+ With `CLAUDE_CONFIG_DIR` set, Claude keeps everything there: its global config is
1844
+ `$CLAUDE_CONFIG_DIR/.claude.json` instead of `~/.claude.json`, and settings,
1845
+ `projects/` transcripts, history and backups sit beside it. `claude auth status`
1846
+ reports both paths (`configDirectory`, `projectsDirectory`). Without the variable,
1847
+ every directory Claude runs in gets an entry in `~/.claude.json` and a transcript
1848
+ folder under `~/.claude/projects/`, so each real-Claude e2e scratch project used
1849
+ to leave both behind.
1850
+
1851
+ An empty config dir is logged out (`loggedIn: false`): Claude does not read the
1852
+ default `Claude Code-credentials` keychain item for it. It does read a
1853
+ `$CLAUDE_CONFIG_DIR/.credentials.json` holding the keychain item's JSON. A copy with
1854
+ `refreshToken` and `refreshTokenExpiresAt` removed logs in (`authMethod:
1855
+ "claude.ai"`), creates no new keychain item, and cannot refresh, so it can never
1856
+ rotate the user's own refresh token. It works until the access token's `expiresAt`,
1857
+ which is why `tests/e2e/claude-home.ts` copies it afresh in each process.
1858
+
1859
+ A set config dir has a keychain item of its own (claude 2.1.310, 2026-10-26):
1860
+ `Claude Code-credentials-` followed by the first eight hex digits of the SHA-256 of
1861
+ the `CLAUDE_CONFIG_DIR` value as written, NFC-normalized, even when it names
1862
+ `~/.claude`. An empty value counts as unset. With `CLAUDE_CONFIG_DIR` set to a fresh
1863
+ dir, `claude auth status` reported `loggedIn: true` exactly while an item of that
1864
+ name existed. The item wins over a `.credentials.json` in the same dir: with both
1865
+ holding different logins, Claude reported the item's. So a user with the variable
1866
+ set may have their login only in that item, and `tests/e2e/claude-home.ts` reads it
1867
+ from there first. The 2.1.310 binary also lets `CLAUDE_SECURESTORAGE_CONFIG_DIR`
1868
+ replace the setting the item is named for (read from the code, not exercised). When
1869
+ it is set, even to an empty value, it decides the name: empty means the default item,
1870
+ anything else that value's hash. Only when it is unset does `CLAUDE_CONFIG_DIR` decide.
1871
+ Claude also passes the variable on to the teammates it spawns. So a Claude started with
1872
+ a private `CLAUDE_CONFIG_DIR` would still use the user's item while the variable is
1873
+ inherited. `tests/e2e/claude-home.ts` reads the user's login from the item that
1874
+ variable names, then removes it from the suite's environment.
1875
+
1876
+ An `ANTHROPIC_API_KEY` in the environment logs any config dir in on its own
1877
+ (`authMethod: "api_key"`), so check a login with it unset. In the TUI, whether that
1878
+ key is used is the global config's `customApiKeyResponses`, which the e2e copy keeps.
1879
+
1880
+ `ready` does not show that Claude got neither credential variable (Claude 2.1.310,
1881
+ 2026-10-26). With the copied login, an invalid `ANTHROPIC_API_KEY` from the launch
1882
+ shell's startup files keeps the TUI from `ready`, but an invalid `ANTHROPIC_AUTH_TOKEN`
1883
+ does not: Claude reaches `ready` with it set. Both variables can also come from the
1884
+ developer's own startup files, after Elwood's environment is handed over. So C-E2E-23
1885
+ unsets both in the launch shell, and a `SessionStart` project hook records which of the
1886
+ two Claude's own environment holds (`tests/e2e/claude-credential-hook.ts`).
1887
+
1888
+ The private dir carries none of the user's `settings.json`, so it shows what a fresh
1889
+ install does: the bypass-permissions dialog appears unless
1890
+ `skipDangerousModePermissionPrompt` is set (see above), and the footer shows Claude's
1891
+ default permission mode. C-E2E-23.
1892
+
1893
+ ### A rate-limited Codex turn stays running for minutes (2026-10-25)
1894
+
1895
+ Codex 0.164.0 retries a `429 Too Many Requests` after the server's retry delay. In
1896
+ Codex's own log (`~/.codex/logs_2.sqlite`, target `codex_core::responses_retry`)
1897
+ the delay for four e2e turns on one account's default model was 110 to 509 s. All
1898
+ four retries were scheduled for the same instant, the end of a usage window. While
1899
+ it waits, Codex paints
1900
+ `• Reconnecting... 1/5 (44s • esc to interrupt)` over
1901
+ `└ Exceeded retry limit, last status: 429 Too Many Requests`. The rollout ends at
1902
+ the user message with no reply, and no `Stop` hook fires. Elwood correctly stays
1903
+ `running`, because the turn has not ended. A real-turn e2e that inherits the
1904
+ developer's `~/.codex` shares that model's limit with every Codex session on the
1905
+ account, so it can wait out a backoff no test timeout covers. A test avoids this by
1906
+ pinning a private `CODEX_HOME` and a small model. `tests/e2e/resume-ready-codex.e2e.ts`
1907
+ does, and bounds its readiness, turn and submission waits so a stuck step fails with
1908
+ its label and the screen.
1909
+
1910
+ A private home whose `config.toml` points
1911
+ `model_provider` at a local `[model_providers.<id>]` with `base_url =
1912
+ "http://127.0.0.1:<port>/v1"` and `wire_api = "responses"` reproduces this
1913
+ without quota. That provider answers every request with 429 and `retry-after: 300`.
1914
+ Codex still lists models from it before the first turn. C-API-28.
1915
+
1916
+ ### Concurrent cold starts race Codex's state migrations (2026-10-25)
1917
+
1918
+ Codex 0.164.0 creates and migrates its SQLite state under `CODEX_HOME` at launch
1919
+ (61 migrations in `state_5.sqlite`, 2 in `goals_1.sqlite`), and nothing serializes
1920
+ those migrations across processes. When two Codex processes start together in a home
1921
+ whose databases are not yet migrated, both try to apply the same migration. The loser
1922
+ exits 1 with `Codex couldn't start because its local database appears to be damaged`,
1923
+ and the cause names the collision, such as `table thread_goals already exists` or
1924
+ `duplicate column name`. Six different migrations collided across the runs below. The
1925
+ exit landed 2 to 10 s after spawn. In every Elwood run that was after `startCodex` had
1926
+ resolved, so Elwood reported `running` → `exited` (`terminal_exited`) and the session
1927
+ never reached `ready`.
1928
+
1929
+ C-CODEX-14's e2e started its two sessions with `Promise.all` in a fresh sandbox. The
1930
+ same start lost a session in 4 of 5 probes and in 5 of 6 e2e runs. The test's readiness
1931
+ poll then ran out its full minute, and the surviving session, which `Promise.all`
1932
+ dropped, kept the test file running. Replaying Elwood's exact launch command through a
1933
+ bare node-pty, with no Elwood code, crashed one of the two in 3 of 4 runs. One launch
1934
+ alone never crashed. Bare pairs with only `--sandbox`, `--ask-for-approval`, and `--cd`
1935
+ never crashed in 16 runs. Adding `-c model_reasoning_effort="low"` alone or
1936
+ `--dangerously-bypass-hook-trust` alone crashed 3 of 3, and `-c features.hooks=true`
1937
+ alone crashed 0 of 3. The runs do not show why the bare pairs escaped, so treat any
1938
+ pair of simultaneous launches as exposed.
1939
+
1940
+ A single launch at load 50 finished every migration about 1.5 s after spawn, as its
1941
+ composer painted. The composer does not prove migration is done: a launch killed one
1942
+ second after its composer appeared left migrations for the next pair, which raced in 1
1943
+ of 4 runs. The e2e now starts the second session after the first reports `ready`, which
1944
+ on a cold start is the 10 s deadline from the first frame. The concurrency it tests is
1945
+ the model switch.
1946
+
1947
+ Production homes already hold migrated databases. The exposure is the first launch of
1948
+ a Codex release that adds a migration, if several sessions start at that moment.
1949
+
919
1950
  ### Codex commentary and final-answer phases (2026-10-20)
920
1951
 
921
1952
  The reported completed `response_item` fixture retains assistant text in an
@@ -955,6 +1986,17 @@ partial model dialog is insufficient. The sanitized Claude frame is retained in
955
1986
  `tests/fixtures/claude-2.1.278/model-cancelled.txt`. With this recognition fix,
956
1987
  Claude also completed automated `listModels` and returned five rows (C-API-55).
957
1988
 
1989
+ ### Idle CLIs do not repaint (2026-10-25)
1990
+
1991
+ A probe started real sessions through Elwood, waited for `ready` plus 3 s, and then
1992
+ counted PTY output chunks while the session sat idle. Codex 0.164.0 emitted none in
1993
+ 15 s idle, and none in the 10 s after `listModels` opened and cancelled its picker.
1994
+ Claude Code 2.1.310 emitted one chunk in each window. Clearing a model-dialog hold
1995
+ therefore cannot wait for a run of new frames, because on Codex none arrive. The
1996
+ evidence has to be the same completed frame read over time. Readings count at
1997
+ least 40 ms apart, so neither bunched readings nor a fast burst of repaints can
1998
+ release a hold early (C-API-55).
1999
+
958
2000
 
959
2001
  ### Cursor provenance for Codex trust clearance
960
2002
 
@@ -979,6 +2021,13 @@ only the production clear write made both tests retain the image and text and fa
979
2021
  This verifies an automation-owned idle composer, not cleanup across caller edits
980
2022
  or blocking dialogs. Codex clipboard text is restored after attachment.
981
2023
 
2024
+ codex-cli 0.164 (2026-10-26) saves the pasted image as `codex-clipboard-<suffix>.png`
2025
+ in its temp dir when the `[Image #1]` chip appears, and clearing the draft leaves that
2026
+ file in place. The draft is never submitted, so no transcript names the copy, and a
2027
+ session removes only the copies its transcript names. So a cancelled Codex image draft
2028
+ leaves its copy in the temp dir. `composer-cleanup.e2e.ts` gives its Codex a `TMPDIR`
2029
+ inside the sandbox home, asserts the copy lands there, and removes it with the sandbox.
2030
+
982
2031
 
983
2032
  Native startup protocol replies also emit xterm `onData`. The actual-session
984
2033
  image-cancellation proof exposed this on both adapters: treating those replies as
@@ -1167,6 +2216,41 @@ bounds, not acceptance or post-acceptance behavior. The temporary Codex auth
1167
2216
  copy was removed after teardown.
1168
2217
 
1169
2218
 
2219
+ ## Claude's temp root (Claude 2.1.310, 2026-10-26)
2220
+
2221
+ Claude keeps its temp files under a root: `CLAUDE_CODE_TMPDIR` when set, else `/tmp`, so
2222
+ the macOS `TMPDIR` does not move it. Measured with real sessions started through Elwood:
2223
+
2224
+ - Under `<root>/claude-<uid>/`, Claude makes one folder per working directory, named for
2225
+ the directory's real path with each character other than a letter or digit replaced by
2226
+ `-`. It holds a folder per session with that session's `scratchpad/`. The folder is
2227
+ still there after `stop()` and after `teardown()`; nothing in Claude removes it. Every
2228
+ e2e scratch project left one in the shared `/tmp/claude-<uid>` until the suite gave each
2229
+ e2e process a private `CLAUDE_CODE_TMPDIR` (C-E2E-23).
2230
+ - Claude's messaging socket is `<root>/cc-socks/<pid>.sock`. The 2.1.310 bundle uses that
2231
+ path only while it is at most 103 bytes, and otherwise falls back to
2232
+ `/tmp/cc-socks-<uid>/<pid>.sock`, so a long root moves the socket back into the shared
2233
+ `/tmp`. The e2e root's prefix is short for that reason.
2234
+ - Claude, run directly in a PTY, removes its socket when it exits on SIGTERM, SIGHUP,
2235
+ `/exit`, or Ctrl-C. A SIGKILL leaves it: after `teardown()`, which SIGKILLs a live session,
2236
+ `/tmp/cc-socks/<pid>.sock` remained for the dead pid. After `stop()`, whose SIGTERM
2237
+ Claude exits on, it did not.
2238
+
2239
+ ## Codex keeps a temp copy of every pasted image (codex-cli 0.164.0, 2026-10-26)
2240
+
2241
+ On Ctrl+V with an image on the clipboard, Codex writes the image to
2242
+ `$TMPDIR/codex-clipboard-<suffix>.png` before the `[Image #N]` chip appears, and nothing
2243
+ in Codex ever removes it: a real attach left the file in place after the turn finished
2244
+ and after `teardown()`. The suffix is six random characters on 0.164.0; an earlier
2245
+ rollout on the same machine names a UUID form (`codex-clipboard-<uuid>.png`). When the message is submitted,
2246
+ the rollout records the path twice: as `{"type":"local_image","path":...}` in the
2247
+ content of an `event_msg` `item_completed` `UserMessage`, and as
2248
+ `<image name=[Image #1] path="...">` text beside the inline `input_image` data in the
2249
+ `response_item` message. The image itself travels inline, so nothing reads the file
2250
+ after submission. Once the CLI exits, Elwood removes the copies the session's transcript
2251
+ names directly in Elwood's own temp directory (C-API-46). A Codex whose login shell sets
2252
+ another `TMPDIR` keeps its copies there, and Elwood does not remove them.
2253
+
1170
2254
  ## Cached image recovery frames (2026-10-01)
1171
2255
 
1172
2256
  The existing captured image-chip and staged-text layouts establish live composer
@@ -1234,6 +2318,78 @@ profile timed out before `codex --version` at 15 seconds; the same pinned binary
1234
2318
  with an empty private profile directory returned its version in 52 ms. This
1235
2319
  preflight failure submitted no model turn. Parent environment values stay intact.
1236
2320
 
2321
+ ### Claude named-session composer rule (2026-10-24)
2322
+
2323
+ `claude --name` (Elwood's `name` option; Coal Harbor names every agent) insets the
2324
+ name into the composer's top rule on Claude Code 2.1.310: `──── Question Asker ─`.
2325
+ The rule below the composer stays plain, and the terminal title becomes
2326
+ `✳ Question Asker` instead of `✳ Claude Code`. Composer recognition required plain
2327
+ rules on both sides, so trust clearance and empty-input observation never matched a
2328
+ named session. In an untrusted folder, autotrust answered the trust prompt but the
2329
+ session stayed `blocked` on the idle composer; in a trusted folder it reached `ready`
2330
+ through hooks and hid the gap. The top rule may now carry one inset label
2331
+ (`src/claude/composer/border.ts`). See `tests/fixtures/claude-2.1.310/`.
2332
+
2333
+ ### Claude permission-mode footers (2026-10-25)
2334
+
2335
+ Startup-only captures from Claude Code 2.1.310 launched with each
2336
+ `--permission-mode`, with no model prompt sent. The footer is the row directly
2337
+ under the composer's bottom rule. With the developer's settings
2338
+ (`editorMode: "vim"`, `verbose: true`):
2339
+
2340
+ ```
2341
+ -- INSERT -- ⏸ manual mode on · ← for agents 0 tokens
2342
+ -- INSERT -- ⏸ plan mode on (shift+tab to cycle) · ← for agents 0 tokens
2343
+ -- INSERT -- ⏵⏵ accept edits on (shift+tab to cycle) · ← for agents 0 tokens
2344
+ -- INSERT -- ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents 0 tokens
2345
+ -- INSERT -- ⏵⏵ don't ask on (shift+tab to cycle) · ← for agents 0 tokens
2346
+ -- INSERT -- ⏵⏵ bypass permissions on (shift+tab to cycle) · ← for agents 0 tokens
2347
+ ```
2348
+
2349
+ The effort level (`● high · /effort`) sits on the next row. With
2350
+ `editorMode: "normal"` and `verbose: false` (passed through `--settings`, never
2351
+ written to user config) there is no `-- INSERT --` and no token count, and the
2352
+ effort level moves to the footer's right edge:
2353
+ `⏸ manual mode on · ? for shortcuts · ← for agents ● high · /effort`. Only
2354
+ manual mode lacks `(shift+tab to cycle)`, and in this layout only manual mode
2355
+ shows `? for shortcuts`. A `default` launch renders `manual mode`. Vim normal mode,
2356
+ after an Escape at the idle composer, drops the `-- INSERT --` prefix. Each
2357
+ effort level has its own glyph: `○ low`, `◐ medium`, `● high`, `◉ xhigh`,
2358
+ `◈ max`. The captures only show `0 tokens`. The binary formats counts with
2359
+ `Intl.NumberFormat` compact notation, then lowercases them: `999`, `1.0k` (or
2360
+ `1k` where it strips `.0`), `12.3k`, `2.1m`. Counts are never digit-grouped.
2361
+
2362
+ Some pieces are transient. Release notes embedded in the 2.1.310 binary say
2363
+ `← for agents` hides 10 seconds after a session opens. In the captures it was still present at 13 s
2364
+ and gone by 27 s. Late frames also lost the effort level: no effort row in the
2365
+ vim layout, an empty right edge in the plain one.
2366
+
2367
+ Elwood accepted only the 2.1.278 auto and don't-ask footers with the agents hint
2368
+ and no right edge, plus the 2.1.305 manual footer. On 2.1.310 only the late
2369
+ plain manual footer, identical to 2.1.305's, still matched. Trust clearance then depended on the version banner, which
2370
+ scrolls out of the viewport in a long session. Model-picker hold release never
2371
+ saw the banner at all, because it reads only the last composer and the rows
2372
+ beneath it. Empty-input observation rejected every 2.1.310 effort row. The shared
2373
+ grammar is `src/claude/composer/footer.ts`, and trust clearance requires the
2374
+ footer directly under the bottom rule, so a quoted footer cannot pass for chrome.
2375
+ Empty-input observation requires the same row. None of the 159 composer frames
2376
+ captured on 2.1.310, and none in the 2.1.278 to 2.1.305 fixtures, has a blank row
2377
+ between the bottom rule and the footer. Overflow rows and the login-renewal notice
2378
+ only ever follow the footer.
2379
+ Sanitized 100x8 bannerless frames for every mode are in
2380
+ `tests/fixtures/claude-2.1.310/mode-footers/`. `tests/e2e/permission-footer-claude.e2e.ts`
2381
+ checks a real bypass-permissions session. C-TRUST-01, C-API-55, C-API-56.
2382
+
2383
+ Narrow terminals are not covered. Below roughly 64 to 90 columns, depending on the
2384
+ mode and layout, Claude cuts the footer mid-hint, sometimes with an ellipsis:
2385
+ `⏵⏵ bypass permissions on (shift+tab to`, `(shift+tab to cycle) · ← fo…`,
2386
+ or just `⏵⏵ bypass permissions`. It also moves the token count and effort level
2387
+ onto their own rows. A cut footer does not prove the composer, so the hold stays
2388
+ until the terminal is widened. The hand-opened 2.1.310 picker itself
2389
+ (`● High effort ←/→ to adjust`, a `↓ 8.` scroll marker, `… +5 models`) falls
2390
+ outside the model-dialog grammar on `main` at the time of writing, so that
2391
+ grammar is the remaining gap before a foreign hold engages on it.
2392
+
1237
2393
  ## Claude question editor captures (2026-10-20)
1238
2394
 
1239
2395
  Claude Code 2.1.305 accepts a single question on Enter. Grouped questions advance
@@ -1254,6 +2410,24 @@ the highlighted option glyph. The captures used private configuration and
1254
2410
  authentication copies with synthetic prompts; see
1255
2411
  `tests/fixtures/claude-2.1.305/questions/`.
1256
2412
 
2413
+ ### Claude 2.1.310 question chip and named-session border (2026-10-24)
2414
+
2415
+ Claude Code 2.1.310 renders a single question's header chip as ` ☐ Test pick `:
2416
+ the highlight's right padding is a written trailing space, so the terminal row
2417
+ keeps it. 2.1.305 rendered ` ☐ Note` with no padding. Exact equality on the tab
2418
+ row therefore rejected every single-question panel on 2.1.310, and Elwood never
2419
+ raised the request even though the native dialog was on screen. The pinned
2420
+ 2.1.305 proof could not see it. Tab matching now ignores trailing whitespace
2421
+ only; the leading space and the header text still have to match exactly.
2422
+
2423
+ A session launched with `--name` (Coal Harbor names every agent) draws its label
2424
+ into the prompt box's bottom border — `──── Question Asker ─` — and that border
2425
+ stays one row under the questionnaire footer. The rule above the header chip
2426
+ stays plain. The questionnaire grammar used to require nothing but blank rows
2427
+ after the footer, so it rejected every question in a named session. It now
2428
+ accepts exactly one labelled border there and still rejects anything else. See
2429
+ `tests/fixtures/claude-2.1.310/questions/`.
2430
+
1257
2431
  ### Claude question dialogs and manual composer (2026-10-20)
1258
2432
 
1259
2433
  Claude Code 2.1.305 renders `AskUserQuestion` as a fenced questionnaire with
@@ -1389,3 +2563,19 @@ Queued editors after the first use `⇧→ prev question`; only the first uses
1389
2563
  frames. If a local skip and Esc collapse coalesce into one output render, the
1390
2564
  smaller badge does not identify the removed question. Preserve the prior focused
1391
2565
  editor count until a matching remaining editor confirms that removal.
2566
+
2567
+ ## Login-shell not-found handlers (2026-10-26)
2568
+
2569
+ A probe runs its CLI through the user's interactive login shell, so a CLI missing
2570
+ from PATH meets whatever not-found handler the startup files define. zsh 5.9 runs
2571
+ `command_not_found_handler` and bash 4.0 and later run `command_not_found_handle`.
2572
+ Ubuntu's `/etc/bash.bashrc` defines the bash one when the `command-not-found`
2573
+ package is installed, to suggest a package. The handler's
2574
+ output goes to the shell's stdout and stderr, which a probe captures, and the
2575
+ handler's return status becomes the probe's. One that returns 1 turns "not found"
2576
+ (127) into an ordinary failure. Measured with a handler printing a marker: zsh 5.9
2577
+ on macOS, and bash 5.2.37 in a Debian trixie container, both captured the marker
2578
+ and reported the handler's status. macOS's bash 3.2 predates the hook. The probe
2579
+ script now runs `unset -f command_not_found_handler command_not_found_handle
2580
+ 2>/dev/null || :` before its command. Each shell ignores the other's name. With it,
2581
+ both shells exit 127 and capture no marker.