Explicit sub-orchestration id collision with a *running* instance hangs the parent silently
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- rust
- Domain
- backend, distributed-systems
Research direction
Start in process_orchestration_item and compare the normal StartOrchestration path with terminal_collision_notifications. Reproduce the shared-child case using schedule_sub_orchestration_with_id, then inspect how the existing history and recorded parent link are handled. Done means the running collision no longer leaves p2 silently waiting, with behavior and tests aligned to the chosen direction.
Written by the indexing model from the issue text.
Description
Summary
When a parent schedules a sub-orchestration with an explicit instance id that already names a running (non-terminal) instance, the parent waits forever. There is no error, no notification, and no log line.
The terminal half of this collision is handled — terminal_collision_notifications in the orchestration dispatcher fails the parent fast when the target id names a terminal instance. The running half has no equivalent.
| Colliding instance is… | Outcome |
|---|---|
| terminal | parent fails fast ✅ |
| running | parent hangs silently ❌ |
Arguably "running" is the likelier half: two parents racing on the same id is most plausible precisely when the child is long-lived.
Reproduction
Two unrelated parents pass the same explicit id to schedule_sub_orchestration_with_id. No reserved marker, no provider bypass — public API only.
let parent = |ctx: OrchestrationContext, _i: String| async move {
ctx.schedule_sub_orchestration_with_id("Child", "shared-child", "x").await
};
// Child parks on an external event so it stays Running.
let child = |ctx: OrchestrationContext, _i: String| async move {
ctx.schedule_wait("Go").await;
Ok("child-done".to_string())
};
client.start_orchestration("p1", "Parent", "").await?; // creates "shared-child", parks
client.start_orchestration("p2", "Parent", "").await?; // same id, still running
Observed:
child status after p1: Running
p2: HUNG (timeout)
p1 final: Completed { output: "ok:child-done" }
p2 final: Running <- forever
p2's StartOrchestration is absorbed as a duplicate start for an existing instance. The child eventually completes and notifies only p1 — its recorded parent. p2 gets nothing.
Why it happens
terminal_collision_notifications is only invoked from the terminal fast-ack path in process_orchestration_item:
if temp_history_mgr.is_completed || temp_history_mgr.is_failed || (…CAN…) {
let orchestrator_items = self.terminal_collision_notifications(&item).await;
…
}
A colliding start against a running instance never reaches that branch. It falls through to normal processing, where it is deduplicated against the existing history and silently dropped.
Scope
Only reachable when the child id is caller-chosen — schedule_sub_orchestration_with_id, schedule_orchestration (detached), or an id already created by Client::start_orchestration. Auto-generated ids cannot collide: they embed both the parent instance id and its execution.
Pre-existing; not introduced by #28. Splitting out so that PR stays scoped.
Possible directions
- Detect in the normal path — when an incoming
StartOrchestrationtargets an instance whose recorded parent link differs from the incoming one, notify the incoming parent withSubOrchFailed. Symmetric with the terminal case, but needs care to avoid the false-positive class that the terminal-path parent comparison originally had (see #28 discussion). - Make it loud instead of silent — cheapest option. Log an error whenever a
StartOrchestrationis discarded for an already-existing instance. Fixes no hang but removes the silence. - Reject at schedule time — not possible in general; the scheduling parent has no way to know the id is taken without a provider read.
- Dominant language
- Rust
- Stars
- 221
- Forks
- 61
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 1
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from microsoft/duroxide
-
One failed session lock renewal loses the session: no retry, no log, no signal to running activitiesOpenbug
Difficulty 4/5 3-5 days Newbie friendliness 30/100
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 72/100
-
bug
Difficulty 5/5 Over a week Newbie friendliness 38/100
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
prune_kv_values_updated_before emits actions in HashMap order; replay fails with `kv clear mismatch`Possibly taken @akhil9tiet claimed this 1 day ago. Openbug
Difficulty 3/5 1-2 days Newbie friendliness 25/100
All issues in microsoft/duroxide
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
[Bug]: Bedrock request metadata forwarding does not work for /embeddingsPossibly taken A pull request linked to this issue is open or already merged. Openbug llm translation
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
pytest plugin: a crashed xdist worker aborts the whole session with INTERNALERRORPossibly taken @hazelxue claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
skillfs: one malformed chat-log line aborts the entire skill-usage analysis (skill_usage_from_chat_logs.py)Possibly taken @zjncs claimed this today. Opencomponent:skillfs
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
agentic-os-org/ANOLISA#6116 · 1 comment ·
Maintainers usually reply within 1 day