Skip to content

refactor(codex): store app-server thread bindings in SQLite plugin state#101210

Merged
steipete merged 12 commits into
mainfrom
claude/hopeful-gagarin-163c61
Jul 7, 2026
Merged

refactor(codex): store app-server thread bindings in SQLite plugin state#101210
steipete merged 12 commits into
mainfrom
claude/hopeful-gagarin-163c61

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Related: #93313

What Problem This Solves

Codex app-server thread bindings are stored as JSON sidecar files next to each session transcript (<sessionFile>.codex-app-server.json), serialized with cross-process file locks that are held across guarded app-server RPCs. This violates the repo's SQLite-only storage rule for runtime state, leaks orphaned sidecar files as sessions rotate, and ties binding ownership to a transcript path instead of the session identity that actually owns the Codex thread.

Why This Change Was Made

This ports the SQLite binding-store portion of #93313 onto current main. Bindings now live in the plugin-state keyed store (namespace app-server-thread-bindings in the shared state DB) as zod-validated rows keyed by owner identity (session:<agent>:<id>, session-key:<agent>:<sha256>, or conversation:<bindingId>). Row-level leases (token + expiry + heartbeat renewal) replace the file locks; a stored session-generation fence (sessionId + retired tombstones + adopt/retire/reclaim) prevents a stale physical session generation from clearing or reclaiming its successor's binding. A new openclaw doctor --fix state migration imports legacy sidecars (schema v1 and v2), records Codex harness ownership on the owning session entry, and archives each imported sidecar as *.migrated; runtime code never reads sidecars. Core additions kept minimal: a plugin-state overflowPolicy: "reject-new" option, agentId on harness reset params and hook/command contexts, previousSessionId on the after-compaction hook, and doctor env threading. The store schema is grafted onto main's current binding shape (schemaVersion-2 plugin-app policy incl. ask, typed service tier, context-engine state); the PR's unrelated runtime refactor (shared clients, turn routing, native context usage accounting) is intentionally not ported.

User Impact

No day-to-day behavior change for Codex sessions. Existing sidecar bindings are imported once by openclaw doctor --fix (also via auto-migration) and the legacy files are archived, so bound threads keep resuming after upgrade; sidecar files no longer accumulate next to transcripts. Session reset, compaction rotation, and conversation rebinding now fence stale generations through the store instead of relying on file presence.

Evidence

  • Blacksmith Testbox (lease tbx_01kwwqk3j24q1n65s1f32485k2): pnpm test extensions/codex — 1,958 tests passed (rebased onto current main, incl. the connected-account app policy variant from Let owner-operated Codex agents use connected account apps #100973 grafted into the store schema); focused batch (plugin-state store, state-migrations, gateway reset cleanup/hooks, auto-reply session, compaction hooks/overflow/timeout, harness registry, hook mappers, plugin commands, session-binding, thread-lifecycle bindings, codex commands, doctor contract) — 8 Vitest shards passed; pnpm build green.
  • Typecheck (tsgo root + extensions lanes), oxlint (prepared runner, src + extensions), oxfmt, and git diff --check all clean.
  • Structured autoreview (gpt-5.5) ran per commit: six commits clean; one accepted finding (doctor migration could scan above stateDir when a session store sits at its exact parent) fixed in fix(codex): keep doctor sidecar scan inside stateDir with a regression test proven to fail pre-fix; one finding (short clear-tombstone TTL on stable keys) rejected as the intended fencing design — recovery is owned by generation ownership checks plus reclaim-generation, and durable fences come from retireSessionGeneration, now documented inline.
  • Codex upstream contract checked in sibling codex checkout: codex-rs/app-server-protocol/src/protocol/v2/thread.rs (ThreadResumeParams.thread_id is the canonical resume handle) and codex-rs/app-server/src/request_processors/thread_processor.rs (thread_resume_inner rehydrates from Codex's own thread store/rollouts), so binding rows only map session identity to threadId and clearing a row never loses Codex-side thread data.
  • Doctor migration proof: doctor-contract-api.test.ts imports a shipped v2 sidecar (incl. destructiveApprovalMode: "ask"), writes session + legacy-conversation rows, records agentHarnessId: "codex" on the session entry, and archives the sidecar.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime agents Agent runtime and tooling extensions: codex size: XL maintainer Maintainer-authored PR labels Jul 6, 2026
@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 6, 2026, 10:01 PM ET / 02:01 UTC.

Summary
The branch moves Codex app-server thread bindings from JSON sidecars to SQLite plugin-state, adds doctor migration, and threads session identity through Codex/gateway hooks and commands.

PR surface: Source +1917, Tests +1795, Docs 0. Total +3712 across 68 files.

Reproducibility: yes. by source inspection: a non-default agent with an unscoped/global session key reaches session_end with ctx.agentId resolved from the key as the default agent, so Codex retires the wrong stable binding row.

Review metrics: 2 noteworthy metrics.

  • Plugin-state API surface: 1 option added. overflowPolicy expands the keyed plugin-state contract and is compatibility-sensitive for bundled and external plugin storage users.
  • Session lifecycle identity surface: 3 identity fields added or used. agentId and previousSessionId are now part of hook/command/reset flows, so owner identity must be consistent across gateway, auto-reply, and Codex retirement paths.

Stored data model
Persistent data-model change detected: migration/backfill/repair: extensions/codex/doctor-contract-api.test.ts, migration/backfill/repair: extensions/codex/doctor-contract-api.ts, migration/backfill/repair: extensions/codex/src/migration/session-binding-sidecars.ts, migration/backfill/repair: src/infra/state-migrations.test.ts, persistent cache schema: extensions/codex/src/command-handlers.ts, persistent cache schema: extensions/codex/src/commands.test.ts, and 16 more. Migration or upgrade compatibility proof is recorded; maintainers should verify it before merge.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Fix owner-agent propagation for session lifecycle hook payloads and Codex binding retirement.
  • Rerun focused gateway/session-hook and Codex binding-store tests on the repaired head.

Risk before merge

  • [P1] The remaining bug can leave a non-default agent's Codex stable session-key binding live after reset/idle/delete/shutdown when the session key is global or otherwise unscoped.
  • [P1] The PR changes persistent Codex binding storage, doctor migration, hook context, and plugin-state API surface, so upgrade/session-state proof remains important after the owner-agent repair.

Maintainer options:

  1. Preserve owner agent in lifecycle hooks (recommended)
    Thread the explicit owner agentId through session_start/session_end payload construction and update Codex retirement/adoption tests for non-default global session keys before merge.
  2. Pause for session hook contract review
    If maintainers want session lifecycle context shape reviewed as a plugin API decision, hold the PR until that hook contract is explicitly accepted.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Fix the remaining owner-agent session lifecycle bug only: make buildSessionStartHookPayload/buildSessionEndHookPayload or their callers preserve the explicit owning agentId when it is known, including gateway reset/delete/shutdown/compaction and auto-reply replacement paths, so Codex session_end retires the actual non-default/global session-key binding. Add focused regression coverage for a non-default agent using a global or otherwise unscoped session key. Do not change the SQLite binding-store schema or unrelated Codex runtime behavior.

Next step before merge

  • [P2] There is one narrow, source-backed repair: preserve explicit owner agentId through session lifecycle payloads before Codex retires stable binding rows.

Security
Cleared: No concrete security or supply-chain concern was found in the reviewed diff; the blocker is session-state ownership correctness.

Review findings

  • [P2] Retire session-key rows with the actual owner agent — extensions/codex/index.ts:217
Review details

Best possible solution:

Keep the SQLite plugin-state binding design, but make session lifecycle hook payloads preserve the explicit owning agent and add regression coverage for non-default/global session keys before merge.

Do we have a high-confidence way to reproduce the issue?

Yes, by source inspection: a non-default agent with an unscoped/global session key reaches session_end with ctx.agentId resolved from the key as the default agent, so Codex retires the wrong stable binding row.

Is this the best way to solve the issue?

No, not yet. The storage direction is the right owner-boundary fix, but the mergeable solution must preserve explicit owner agent identity through session lifecycle hook payloads before Codex uses it for binding retirement.

Full review comments:

  • [P2] Retire session-key rows with the actual owner agent — extensions/codex/index.ts:217
    For global or otherwise unscoped session keys, ctx.agentId is derived from the session key and falls back to the default agent even when gateway already knows the retiring session belongs to a different agent. This late-discovered issue was present in the previous reviewed head: the Codex hook then calls retireSessionGeneration on session-key:<default>:..., leaving the real non-default agent binding live after reset/idle/delete/shutdown and allowing stale thread resumes. Thread the explicit owner agent through the session_end payload before using it here.
    Confidence: 0.9
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.89

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against f3106057e62b.

Label changes

Label justifications:

  • P2: This is a bounded but compatibility-sensitive Codex/session-state refactor with one concrete remaining correctness blocker.
  • merge-risk: 🚨 compatibility: The diff changes plugin-state options, hook context fields, doctor migration behavior, and public plugin-facing docs/contracts.
  • merge-risk: 🚨 session-state: The diff migrates Codex thread bindings and changes session-generation retirement/adoption, and the remaining bug can leave stale bindings live for the wrong agent.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes terminal/Testbox proof for Codex tests, focused shards, build, typecheck, lint, formatting, autoreview, and doctor migration behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal/Testbox proof for Codex tests, focused shards, build, typecheck, lint, formatting, autoreview, and doctor migration behavior.
Evidence reviewed

PR surface:

Source +1917, Tests +1795, Docs 0. Total +3712 across 68 files.

View PR surface stats
Area Files Added Removed Net
Source 37 3422 1505 +1917
Tests 30 3104 1309 +1795
Docs 1 1 1 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 68 6527 2815 +3712

Acceptance criteria:

  • [P1] pnpm test src/auto-reply/reply/session-hooks-context.test.ts src/auto-reply/reply/session-updates.lifecycle.test.ts src/gateway/server.sessions.reset-hooks.test.ts src/gateway/drain-active-sessions-for-shutdown.test.ts.
  • [P1] pnpm test extensions/codex/index.test.ts extensions/codex/src/app-server/session-binding.test.ts.
  • [P1] pnpm test extensions/codex.

What I checked:

  • PR head session_end retirement uses hook context agentId: At the current PR head, Codex builds the retirement identity from ctx.agentId in the session_end hook; when that context is resolved from an unscoped/global key, it can point at the default agent instead of the session owner. (extensions/codex/index.ts:217, dff7a41fed47)
  • Session hook context derives agentId from sessionKey only: buildSessionHookContext resolves agentId from sessionKey and config; it has no parameter for the explicit owner already known by gateway/session lifecycle callers. (src/auto-reply/reply/session-hooks.ts:25, dff7a41fed47)
  • Gateway has owner agentId but does not pass it to the hook payload: emitGatewaySessionEndPluginHook accepts params.agentId and uses it for transcript resolution, but buildSessionEndHookPayload receives only sessionId/sessionKey/cfg, so the plugin hook context loses the explicit owner. (src/gateway/session-reset-service.ts:197, dff7a41fed47)
  • Binding retirement conflicts on wrong generation owner: retireSessionGeneration returns conflict when ownsStoredSessionGeneration is false, so a wrong agent identity leaves the actual stable session-key row live. (extensions/codex/src/app-server/session-binding.ts:625, dff7a41fed47)
  • Re-review continuity: The same session_end ctx.agentId line existed at the prior ClawSweeper-reviewed SHA, so the remaining finding is a late discovery rather than a regression introduced by the latest commits. (extensions/codex/index.ts:217, 641cd40dbe72)
  • Previous contract blockers appear repaired: The migration no longer imports the deprecated config-runtime subpath, and the bundled hook guard now declares Codex after_compaction/session_end registrations. (extensions/codex/src/migration/session-binding-sidecars.ts:4, dff7a41fed47)

Likely related people:

  • steipete: Current-main history shows recent session-thread management work in the gateway/session surface, and this PR ports the narrower Codex binding-store subset from the same author's larger closed refactor. (role: recent area contributor; confidence: high; commits: 6df0fb818d67, 7641ec8ba7b7; files: src/gateway/session-reset-service.ts, extensions/codex/src/app-server/session-binding.ts, extensions/codex/index.ts)
  • pash-openai: Merged adjacent connected-account app policy into the Codex binding schema that this PR migrates and preserves. (role: adjacent owner; confidence: medium; commits: 806a116f9d20; files: extensions/codex/src/app-server/session-binding.ts)
  • ZengWen-DT: Recently changed gateway reset behavior for multi-agent rooms, which is adjacent to the remaining owner-agent session_end bug. (role: recent area contributor; confidence: medium; commits: bffecbff1d2b; files: src/gateway/session-reset-service.ts)
  • vincentkoc: Recent current-main history touches the auto-reply session hook payload helper that needs the owner-agent threading repair. (role: recent adjacent contributor; confidence: low; commits: 1380a9e09400; files: src/auto-reply/reply/session-hooks.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-06T22:59:24.683Z sha 641cd40 :: needs changes before merge. :: [P2] Use a supported SDK subpath for the migration | [P2] Keep the doctor contract barrel lightweight | [P2] Declare the new Codex hook registrations intentionally

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels Jul 6, 2026
@steipete steipete self-assigned this Jul 6, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation plugin: file-transfer labels Jul 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f999d6d03c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/codex/index.ts
sessionBindingIdentity({
sessionId: event.sessionId,
...(sessionKey ? { sessionKey } : {}),
...(ctx.agentId ? { agentId: ctx.agentId } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retire global-session bindings under the owning agent

When a non-default agent uses a global session key, gateway session_end emissions carry the real owner as agentId, but the session hook context derives ctx.agentId only from the session key/config, so ctx.agentId falls back to the default agent for keys like global. This retires the wrong session-key:<agent>:<hash> row here, leaving the Codex SQLite binding for the actual agent live after idle/delete/shutdown and allowing stale thread resumes.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 0d98109 into main Jul 7, 2026
102 checks passed
@steipete
steipete deleted the claude/hopeful-gagarin-163c61 branch July 7, 2026 02:03
@steipete

steipete commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
…ate (openclaw#101210)

* test(codex): shorten placeholder auth fixture strings

* feat(plugin-state): add reject-new overflow policy for keyed namespaces

* feat(agents): thread agent identity and session generation through reset and hooks

* refactor(codex): add SQLite-backed app-server thread binding store

* refactor(codex): move app-server runtime callers onto the binding store

* refactor(codex): move conversation, entry, and thread-tool flows onto the binding store

* refactor(codex): move command handlers onto the binding store

* feat(codex): import legacy binding sidecars via doctor state migration

* fix(codex): keep doctor sidecar scan inside stateDir

* fix(codex): make binding migration landable

* chore(changelog): defer release note

* test(plugin-state): stabilize durable-capacity ordering
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…ate (openclaw#101210)

* test(codex): shorten placeholder auth fixture strings

* feat(plugin-state): add reject-new overflow policy for keyed namespaces

* feat(agents): thread agent identity and session generation through reset and hooks

* refactor(codex): add SQLite-backed app-server thread binding store

* refactor(codex): move app-server runtime callers onto the binding store

* refactor(codex): move conversation, entry, and thread-tool flows onto the binding store

* refactor(codex): move command handlers onto the binding store

* feat(codex): import legacy binding sidecars via doctor state migration

* fix(codex): keep doctor sidecar scan inside stateDir

* fix(codex): make binding migration landable

* chore(changelog): defer release note

* test(plugin-state): stabilize durable-capacity ordering
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation extensions: codex gateway Gateway runtime maintainer Maintainer-authored PR merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. plugin: file-transfer proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: XL status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant