Severity: Low · Area: Lens: concurrency · Category: race-condition
Location: services/agent/selene_agent/orchestrator.py:669
What's wrong
unique_id = f"query_{int(time.time())}" gives content-extracted tool calls ids of the form call_query_, which collide when two sessions extract tool calls in the same second with the same iteration/index. The companion-upload registry is keyed by tool_call_id: register_pending_upload (api/companion.py:50-63) cancels and replaces the prior future on a duplicate key, and _await_companion_upload_payload's CancelledError/timeout cleanup (orchestrator.py:1113-1132) pops whatever future currently occupies the key — which after replacement is the OTHER session's future — so the surviving waiter's upload gets a 410 and its tool call times out; a mistimed upload can also resolve the wrong session's future, delivering one user's photo to another session's tool result.
How it fails
Two phones ask Selene to take a photo within the same second, and GLM emits the tool calls via the <tool_call> content-tag fallback on iteration 1 for both. Both ids are call_query_1720000000_1_0: session B's registration cancels session A's waiter, A's cleanup pops B's future, B's upload returns 410 to the phone, and B's take_photo reports a bogus timeout.
Suggested fix
Use uuid4 (or session_id + uuid) when synthesizing tool_call ids, and make the cleanup paths pop the registry entry only when it is the same future object that the waiter registered.
Adversarial verification — both skeptics confirmed
Skeptic 1 (confirmed) — Confirmed end-to-end in code. orchestrator.py:669 mints unique_id from int(time.time()) at turn start and line 792 synthesizes ids as call_query_ with no session component, so cross-session collisions are possible. The pending-upload registry is a module-global dict shared by all sessions in the single uvicorn pr…
Skeptic 2 (confirmed) — The finding is mechanically accurate on every link of the chain. orchestrator.py:669 mints unique_id from int(time.time()) once per turn, and line 792 synthesizes content-extracted tool_call ids as call_query_ — no session component, no randomness — so two sessions hitting the <tool_call> content fallback in the…
Filed from a multi-agent audit of 855f5cc: 16 reviewers over ~42k lines produced 170 raw findings; each was handed to 2 independent agents prompted to refute it, and only findings both confirmed were kept (19 refuted, 10 split-verdict, 127 unique confirmed). Line numbers are 1-indexed against 855f5cc and will drift as the code changes.
Severity: Low · Area: Lens: concurrency · Category:
race-conditionLocation:
services/agent/selene_agent/orchestrator.py:669What's wrong
unique_id = f"query_{int(time.time())}" gives content-extracted tool calls ids of the form call_query_, which collide when two sessions extract tool calls in the same second with the same iteration/index. The companion-upload registry is keyed by tool_call_id: register_pending_upload (api/companion.py:50-63) cancels and replaces the prior future on a duplicate key, and _await_companion_upload_payload's CancelledError/timeout cleanup (orchestrator.py:1113-1132) pops whatever future currently occupies the key — which after replacement is the OTHER session's future — so the surviving waiter's upload gets a 410 and its tool call times out; a mistimed upload can also resolve the wrong session's future, delivering one user's photo to another session's tool result.
How it fails
Two phones ask Selene to take a photo within the same second, and GLM emits the tool calls via the <tool_call> content-tag fallback on iteration 1 for both. Both ids are call_query_1720000000_1_0: session B's registration cancels session A's waiter, A's cleanup pops B's future, B's upload returns 410 to the phone, and B's take_photo reports a bogus timeout.
Suggested fix
Use uuid4 (or session_id + uuid) when synthesizing tool_call ids, and make the cleanup paths pop the registry entry only when it is the same future object that the waiter registered.
Adversarial verification — both skeptics confirmed
Filed from a multi-agent audit of
855f5cc: 16 reviewers over ~42k lines produced 170 raw findings; each was handed to 2 independent agents prompted to refute it, and only findings both confirmed were kept (19 refuted, 10 split-verdict, 127 unique confirmed). Line numbers are 1-indexed against855f5ccand will drift as the code changes.