[Bug]: Codex configuration refresh skips unsubscribe when retained owner is missing
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 390k
- Forks
- 82k
- PR merge metrics
- PR metrics pending
Description
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
An ordinary Codex configuration refresh can skip physical unsubscribe when its local retained-owner record is absent, causing an otherwise idle thread to fail the unload-confirmation guard.
Steps to reproduce
Deterministic repository-harness reproduction on release v2026.9.5:
- Use the real leased app-server wire client fixture in
extensions/codex/src/app-server/thread-lifecycle-policy-refresh.test-support.ts. - Start a thread through
startOrResumeThreadwith initial developer instructions. Keep its physical connection subscribed and its durable binding, but omit the process-localretainCodexAppServerLiveThreadregistration. - Have
thread/readreportidle. Model the upstream protocol by emittingthread/status/changed: notLoadedduring override resume only after the connection unsubscribed and no other subscriber remains. - Resume through
startOrResumeThreadwith replacement developer instructions. - On the unmodified release, the missing-owner regression fails with
Codex did not confirm unloading its previous configuration: the physical unsubscribe was skipped.
This explicitly constructs the missing-retention state. The production action that creates that state has not been conclusively attributed.
Expected behavior
Reconcile the exact bound connection's subscription before configuration refresh. Preserve the unload-confirmation guard and reject changes when another subscriber prevents cold rebuilding. Do not send duplicate unsubscribes after an acknowledged release.
The existing retained-owner path and inspected Codex rust-v0.154.0 idle/unsubscribed override-resume contract provide the reference behavior.
Actual behavior
The ordinary resume callback discards the failed retained-release result. The native thread stays subscribed, override resume preserves its loaded configuration, and OpenClaw refuses the turn because no notLoaded notification was observed.
OpenClaw version
2026.9.5; isolated source ec9c1a13db8938e5a3eaa51fca2e981cde2395a9.
Operating system
Ubuntu 26.04.1 LTS.
Install method
Affected npm installation; reproduction and candidate validation in an isolated pnpm release checkout.
Model
Model-independent synthetic app-server wire fixture; no inference required for this reproduction.
Provider / routing chain
OpenClaw Codex harness -> leased app-server protocol fixture. Separate protocol probes used installed Codex 0.154.0.
Logs
Baseline + missing-retained-owner regression:
Codex did not confirm unloading its previous configuration
Candidate validation:
thread-lifecycle.binding.test.ts: 247/247 passed
client-runtime.test.ts: 43/43 passed
Screenshots, recordings, and evidence
A local candidate records acknowledged per-connection release/resubscribe facts and only performs the untracked fallback after observing a loaded thread without a known successful release. Verification includes missing-owner recovery, sibling-subscriber refusal, native identity preservation, and 100 rounds across eight owners with exactly 800 unsubscribes. Extension typecheck passed; repository autoreview was scoped-clean at P1. The candidate is not installed or released.
Source boundaries: ordinary resume, bound release.
Impact and severity
The reproduced edge case blocks resuming an idle bound session with updated configuration. Similar guard errors were observed across several production sessions, but per-turn attribution to this exact missing-retention state remains unproven. No data loss is established.
Additional information
Related but distinct: #143187 concerns stale cleanup deleting a successor binding; #148837 concerns settled systemError. The broader reload/catalog outage reported in #152275 is not proven to be fixed by this candidate. Do not remove the unload guard or assume that a blind delay repairs this ownership issue. Introducing commit and last-known-good version: NOT_ENOUGH_INFO.
Contributor 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.
Research direction
Start with the ordinary resume path in extensions/codex/src/app-server/thread-lifecycle-run.ts and the bound release path in extensions/codex/src/app-server/thread-lifecycle-warm.ts, then inspect the real wire-client fixture named in the reproduction. Use thread-lifecycle.binding.test.ts and client-runtime.test.ts to verify missing-owner recovery, subscriber refusal, identity preservation, and unsubscribe counts while preserving the unload-confirmation guard.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 44/100