Adom Hydrogen
Public Made by Adomby adom
The whole Adom experience as one signed native app: your editor, your AI agent, and the full electronics design toolchain, running locally on your own machine.
AI Threads census attaches the wrong Codex conversation id to a tab (and a send could land in the wrong thread)
What happened
On Adom Hydrogen v1.0.601 (DESKTOP-7UQJT50, Adom Bridge 2.1.174 embedded), a remote AI drove two Codex threads through hd_api → /ai-threads/*. Later, GET /ai-threads attached the wrong conversation id to a tab. The other conversation's id disappeared from the list.
Both tabs are Codex webview tabs in editor group 0:
| Tab | Conversation (Codex session) | Opened with |
|---|---|---|
| A | 01a1326a-13b8-7182-af2c-549acb571465 (a one-line connectivity test) |
run-prompt {new:true, type:"codex", name:"Claude -> Codex test", tags:["orchestrated-by:claude-h2o"]} |
| B | 01a132d7-7b67-7400-8b61-7d1b60c27746 (a long-running takeover thread with an active Codex /goal) |
run-prompt {new:true, type:"codex", name:"Playwright HP takeover", ...}, later tagged with POST /ai-threads/tag {ai:"codex", sid:"01a132d7-…", add:[...]} |
About 01:20 UTC (correct):
g0t0 codex label "Codex" sid null
g0t1 codex label "Codex" sid 01a132d7-… tags [handoff:playwright-hp-2026-10-13, orchestrated-by:claude-h2o]
About 03:10 UTC (wrong):
g0t0 codex label "Codex" sid null
g0t1 codex label "Claude -> Codex test" sid 01a1326a-… tags [orchestrated-by:claude-h2o] foreground:true
A screenshot taken at the same moment shows that g0t1 is still conversation B. Its header reads "Review Playwright takeover handoff", its body shows thread 01a132d7-…, and it shows "Pursuing goal …". So the census gave B's tab A's id, name and tags, and B's id is no longer on any row.
Things that happened to tab B between the two reads:
POST /ai-threads/inject {tid:"g0t1", prompt:"/goal …", submit:true}(a Codex slash command). The answer wasok:false, "typed and submitted, but the prompt never appeared in the conversation". That's expected, because a slash command never becomes a message, but the goal was set.- Codex then showed an inline approval card ("Allow registry checks …", Deny / Allow once). The card replaces the composer.
Why it matters
The skill tells callers to store sid and target by it. In this state, run-prompt {sid:"01a1326a-…"} or inject on the row labelled "Claude -> Codex test" would have typed into conversation B, the wrong one. That conversation was running autonomously under a goal. The caller only caught it by taking a screenshot before sending.
Related, same session
- Codex delivery reported but not sent (looks like #84, but this one claims success).
run-prompt {new:true, type:"codex", submit:true, background:true}wenttab_found → submitted → delivered, withinject.delivered_in_conversation:trueandsubmitted:true. But the prompt was still in the composer, selected and unsent, and no Codex rollout file existed until a secondinject {tid, submit:true}sent it. The job also said "no Codex hook event arrived within 30s … install the hooks". The hooks were installed and trusted. They never fired because the prompt was never sent, so that hint pointed at the wrong thing. - Long multi-line prompt refused after typing. A 14.8 KB multi-line Markdown prompt to a new Codex tab ended
inject_failed: "the chat box still holds other text, refusing to send it". The full text was in the composer. The exact-match check seems to fail after the composer re-wraps or normalizes long text. A short single-paragraph prompt into the same tab worked.
What would help
- Re-key a row's identity to whatever conversation the webview currently shows (for Codex, the visible thread id or header) rather than carrying a stale binding over. If they disagree, return
sid:nullwithsummary.identitysaying so, rather than a confident wrongsid. - Have
inject/run-prompt {sid}refuse when the target tab's live conversation id doesn't match the requested sid. - Count Codex
deliveredonly once a new user message appears in the conversation, or the rollout grows. Don't count the send click. - Compare the composer text with whitespace normalized (or by length plus hash of non-whitespace) for long prompts. Or report the mismatch with a diff instead of a bare refusal.
- Let
injectdetect an inline approval card replacing the composer. Reportneeds_youwith the card's text instead of trying to type.