Repository navigation
Task subagent shutdown releases the parent's poll lock and overwrites its DM-owner claim #83
Description
Problem
A native omp task subagent can release the parent bridge's bot.lock and replace the parent's dm-owner.json identity during session_shutdown, even though the child skipped bridge startup. The parent's actual poller remains alive.
Parent and child share a PID and the module-scoped lock nonce, but have separate extension closures and Poller instances. The child's stopBot() stops only its own unused poller while releasing the shared parent's lock. The preceding refreshTopicClaim(ctx) also replaces the DM owner's durable session identity because its PID matches.
This leaves a live parent poller without its lock, allowing another session to acquire it, and points durable untopiced-DM ownership at the ended child. This is the unguarded session_shutdown path, not the agent_end path fixed by #65 for #64.
Repro
An isolated offline harness used two real exported telegramExtension() factories and real Poller instances in one process:
- Use a temporary
HOMEandOMP_TELEGRAM_STATE_DIR, with enabled test state configured for session poller fallback rather than the standalone daemon. Capture each factory's event handlers separately. Give parent and child distinct session IDs and file paths. The child hashasUI: falseandyieldin its active tools. - Intercept global
fetch: return stub responses forgetMeandsetMyCommands, and leavegetUpdatespending until itsAbortSignalis aborted. Do not replace the pollers or the filesystem lock functions, and do not contact Telegram. - Emit the parent's
session_start. It acquires the real temporary poll lock, writes the parent DM-owner identity, and starts a pendinggetUpdatesrequest. - Emit the child's
session_start. The existing task-subagent guard skips bridge startup. - Emit and await the child's
session_shutdown.
Observed after step 5:
| State | Expected | Actual |
|---|---|---|
bot.lock |
Still held by parent | Removed |
dm-owner.json session identity |
Parent | Child |
Parent getUpdates signal |
Not aborted | Not aborted: parent poller is still alive |
As a control, the real parent's shutdown aborted the parent's pending request and left no poll lock. Adding the same early task-subagent guard used at startup to session_shutdown preserved the parent lock and DM owner in an isolated local fix, while normal parent cleanup still worked.
These observations are from the isolated reproduction, not instrumentation of an original live Telegram interruption.
Acceptance criteria
- A task subagent's shutdown cannot release the parent's poll lock or overwrite the parent's DM-owner identity.
- The parent poller remains protected by its lock until the parent releases it.
- Normal parent shutdown still stops and drains its poller and releases its own lock.
- A regression covers two separate real extension factories in the same process, not only parent/child contexts passed to one closure.
Entry points
All references are against v0.13.0:
session_starthas a task-subagent guard, butsession_shutdowndoes not.stopBot()stops its closure-local poller and then releases the lock.PROCESS_LOCK_NONCEis module-scoped, andreleaseLock()defaults to that nonce andprocess.pid.refreshTopicClaim()refreshes the DM owner on a PID match, using the child context's durable identity.
Environment
- omp 18.2.4
- omp-telegram 0.13.0 (
v0.13.0, commit0ce609d7e019f32a7ea734b35aa87dbb1190f990) - Bun 1.3.13
- Linux x86_64
Activity
Release 0.14.0: acknowledged routing, session cards, delivery receipts
TerrifiedBug commentedon Sep 23, 2026
Fixed in 55f4fd0, released in v0.14.0. session_shutdown now has the same task-subagent guard as session_start, so a child's shutdown can't release the parent's poll lock or overwrite dm-owner.json. Regression test runs two real extension factories in one process, per the acceptance criteria: after the child shuts down the lock survives, the DM owner stays the parent, and the parent poller's getUpdates signal is not aborted. Thanks for the unusually precise repro — it made both fixes quick.