
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
dsh-codex-workflow
Advanced tools
DeepSeek Harness plugin that uses Codex to plan and review while DSH executes.
DeepSeek Harness plugin that gives Codex read-only planning/review roles while DSH remains the sole executor. Two flows share the same workflow engine:
From a source checkout, run pnpm demo and open http://127.0.0.1:4175 for a standalone local visual demo. It uses simulated events to demonstrate planning, execution, review and same-thread recovery without connecting to DSH or a model. See the demo guide.
codex_workflow_start for complex development tasks, or the user can request it explicitly; implementation and review still run through the same workflow engine.No browser is opened or controlled anywhere in the product path; no network listener, MCP, hooks, or skills are involved. Browser clicking is a development-only workaround and is not part of the plugin.
Planner and Reviewer turns run through the NPM Codex CLI's codex app-server --stdio, but plugin-created threads use thread/start { ephemeral: true }. They remain available for the current App Server process, do not receive Desktop names, and do not appear in thread/list. Failure recovery and clarification answers require the original process and handle. A lost original thread reports an error; neither path reconstructs a replacement Planner. Structured conversion and authority alignment continue to use codex exec --ephemeral or an equivalent private App Server turn.
Planner and Reviewer runs are published through DSH's subagents.start() using the codex-workflow provider. Each gets a native child Session (origin: "subagent") while the NPM Codex App Server executes the task in an ephemeral thread. The child Session receives native assistant stream frames and a final message. The browser opens DSH's built-in subagentchat right-sidebar resource for that child, so Codex appears as a child agent conversation. The codexWorkflow projection retains workflow status and final review metadata; it no longer serves as the primary live renderer. Private reasoning text and raw tool output are not forwarded. The plugin never opens or focuses Codex Desktop.
Concurrent SDK callers share one App Server initialization, with independent cancellation of each caller's wait. Cancelling one observer preserves the original process for observers still waiting; only the last withdrawal abandons initialization and cleans its original process. A later independent request waits for that cleanup before initializing again. Permanent plugin stop prevents another bootstrap. These initialization rules do not restore a lost ephemeral Planner or authorize a replacement workflow.
Each ordinary control RPC releases its pending entry, timer and abort listener once on response, error, timeout, cancellation or stop. Stdin stream errors belong to the original App Server child; a late error after stop cannot crash the host or invalidate a successor. Ordinary write backpressure keeps the request pending. Local write completion and stream cleanup do not confirm that a remote model turn has stopped.
Persistent-source conversion forks also retain their original thread/fork creation reply after caller abort or timeout. Repeating the same conversion waits for that reply and uses the original fork; changed conversion input and simultaneous consumers are rejected. No model starts before creation is confirmed. An abandoned public fork call releases only its own late subscription; a failed unsubscribe keeps its cleanup barrier until an actual ACK, with concurrent cleanup retries sharing one flight. Healthy explicit public fork calls remain independent. Default ephemeral Planner/Reviewer conversions run directly on their original threads and do not need a fork. These are process-local resource rules, not recovery after process loss. See the fork creation research.
Subscription cleanup requires a valid thread/unsubscribe response status: unsubscribed, notSubscribed or notLoaded. Missing, unknown or null responses preserve the original local owner and usage. An abandoned fork stays guarded until a valid original acknowledgement, so malformed replies cannot authorize another fork or source model turn. Subscription release does not prove that an active turn stopped. See the response validation research.
Completed persistent-source conversions retain their output separately when fork cleanup fails. The first successful result remains valid; a same-context retry cleans the original fork and returns a protected cached copy without repeating the model or its callbacks. Changed conversion inputs, owners or a lost original process cannot borrow that cache. Cleanup observers share one flight, and withdrawing one observer leaves the others intact. Pending resources prevent idle shutdown; explicit source unsubscribe may finish its own fork cleanup. External CLI handoff reports unconfirmed cleanup instead of closing an active or uncertain server. This process-local cache cannot reconstruct lost ephemeral threads. See the completed-task recovery research.
An accepted model start has a separate lifetime from its caller's wait. If turn/start or review/start has been submitted but its reply has not arrived, caller timeout or cancellation retains the original reply route, child, thread owner and stage. Status reports start_receipt_pending with model continuation unavailable. For DSH-led planning and foreground review, wait for the reply or use codex_workflow_resume only to inspect the retained operation. A genuine late reply lets resume reattach the original turn with fresh callbacks and no replacement model request. Bridge reviews use their original submission's background retry for the same adoption; they do not use foreground resume or require resubmitting unchanged material. start_receipt_invalid keeps an unusable reply guarded until original cleanup is confirmed. Cancel/stop release ownership only after actual completion, exact interruption ACK or verified exit of the captured original process instances. These rules never reconstruct a lost ephemeral thread.
Outstanding fork creation also retains its captured original process tree. SDK stop confirms those instances have exited, including a backend surviving launcher exit; signal delivery alone is insufficient. Safe termination uses the opened original Windows process handle with birth validation, or Linux pidfds through an existing Python 3.9+ runtime. Missing evidence or unavailable safe APIs keep cleanup unconfirmed. A later stop retries only that same cleanup without bootstrapping another server or model. Successful repeated stops retain their shared settled promise. See the process lifetime research.
Recoverable idle shutdown also confirms the captured original process tree before reporting external CLI handoff complete or allowing a successor. A launcher exit alone is insufficient. Failed retirement remains owned for cleanup of those same instances; a later explicit start first awaits that cleanup. Cancelling one caller only withdraws its wait, and permanent stop keeps its stopped latch through cleanup retries. These process rules do not recreate a lost ephemeral Planner or Reviewer. See the idle retirement research.
Rejected initialization keeps the same retirement responsibility. The original handshake error remains primary; auxiliary cleanup failure preserves its original process barrier. A later explicit startup waits for the rejected attempt's cleanup and settlement, then starts independently. It cannot rejoin the old failed initializer or bootstrap over an unknown lost-launcher tree. A genuine spawn failure without an OS PID does not invent a process record. See the initializer retirement research.
Permanent SDK stop also retains ordinary App Server backend ownership when no fork was created. It shares one original retirement census and close with initializer/idle cleanup, settles pending observers, and confirms captured original instances exited before returning success. Denied cleanup permits only another stop of those same instances; missing evidence remains unconfirmed and is never backfilled after launcher exit. The stopped client cannot bootstrap a successor. See the permanent-stop research.
Successful initialization records the original process identities before admitting work. An unexpected launcher exit retains that ownership: cleanup discovers later descendants only through surviving originals with matching births, confirms captured instances exited, then permits an explicitly requested successor. Unknown original identities or denied cleanup block this path; a reused PID cannot lend its descendants to the old cleanup. Identity collection is part of initialization and cannot be interrupted by the idle timer. A fresh metadata process still cannot restore an old ephemeral Planner/Reviewer. See the unexpected-exit research.
Closing or failing the original stdout invalidates RPC availability even if its OS process is still alive. Original observers receive the first output failure, pending model receipts and unconfirmed turns retain cleanup ownership, and further model/metadata calls cannot silently bootstrap another server. An ordinary close allows a separate 100ms exit-observation window so a real process exit keeps its exit code and stderr; explicit read errors are handled directly. Retired stream errors cannot invalidate a successor. Physical retirement still requires the original process-tree proof. See the output lifetime research.
On Windows, kill(pid, 0) can report EPERM even after an old PID disappears. For complete saved birth identities, a successful identity query that omits that PID triggers a separate bounded .NET process-absence check. Only a validated absence receipt confirms it; EPERM, omitted rows, denied handles and failed helpers alone remain unknown. Descendant refresh uses the same check and never invents missing original identities or signals a process during inspection. See the absence-proof research.
Original stdin and stdout failures share one first-cause transport guard. An initialize ACK does not publish readiness if stdin fails while original process identities are still being collected. Shared startup observers receive that same physical failure; cancelling one observer keeps its own cancellation reason without hiding the failure from another. After readiness, broken stdin invalidates model/RPC availability while retaining its process and exact active-turn cleanup responsibility. A later output error cannot replace the first input cause, and retired stream events cannot poison a successor. See the initialization transport research.
Node spawn can return a ChildProcess without stdio on EMFILE/ENFILE. The transport keeps that original child and skips readline construction until a real output exists; the SDK installs error/lifecycle listeners and waits for the original failure receipt before any RPC. That wait uses the existing control-RPC budget and caller cancellation. No-PID failures do not invent cleanup records; an actual child with incomplete stdio is retired using its original process evidence. A late failed receipt cannot invalidate an independent successor. See the missing-stdio research.
stderr is a diagnostic channel. A Readable error there is consumed while healthy stdin/stdout RPCs, initialization and the original thread remain usable. If that current process later exits, its diagnostic notes stderr diagnostic stream unavailable; the private stream-error contents are not appended. A retired stderr error cannot add that marker to a successor. Original process-tree retirement remains required. See the diagnostic-stream research.
Each original child decodes stderr incrementally as UTF-8, preserving Chinese characters and emoji split across OS chunks. Its diagnostic flushes observed bytes after its stderr ends or the original exit drain reaches its 100ms bound. The existing 16,384 UTF-16-unit tail budget remains; clipping drops a partial leading surrogate rather than showing half an emoji. Truly incomplete source bytes use Node's replacement-character semantics. Late retired data still cannot enter successor errors. See the UTF-8 diagnostic research.
The CLI audit dispatcher separately enforces its maxOutputBytes retention limit (default 64 KiB per stream): stdout overflow rejects the run, while stderr keeps only the newest complete UTF-8 code points within the limit. Both streams check final decoder output, including replacement characters for genuinely incomplete source bytes. This bounds retained diagnostics, not total process memory or OS pipe buffering; it preserves the existing CLI thread and error classification paths.
CLI stderr Readable errors are consumed separately from model output. Healthy stdout results still complete on the original process; an actual failed exit keeps its original classification and adds a fixed diagnostic-stream-unavailable note outside retained source bytes. Raw stream error messages cannot change model retry classification, and retired stderr errors cannot annotate a later process's failure. This does not repair a broken pipe or restore lost diagnostics.
CLI stdin/stdout errors affect prompt delivery or model output. The dispatcher consumes these events, preserves the first original error or prior cancellation, terminates that original child, and waits for its close and any admitted thread identity binding before rejecting. Synchronous prompt-write failures follow the same cleanup path. Output received after a pending failure cannot bind a new thread identity; retired stream errors are consumed without affecting a later independent process. This handles explicit stream errors, not silent pipe stalls or proof that all descendants have exited.
Caller cancellation remains observed while an already closed CLI process's original thread identity is still being saved. The dispatcher waits for that admitted binding to settle, then reports the first pending error or original cancellation and removes its abort listener. Process close does not end the binding's ownership; cancellation does not skip persistence or start a replacement. Model timers still stop at process close, so this is not a new hard deadline for a binding callback that never settles.
When a CLI caller waits for another run on the same Reviewer thread, its AbortSignal can cancel that wait before the owner completes. The queued caller creates no child and cannot kill the owner's process, cancel its binding or abort another waiter. Its own listener is removed on cancellation or admission; unrelated threads remain independent. This is caller-signal cancellation, not a new queue scheduler, fairness guarantee or rewrite of workflow/submission cancellation.
Known CLI thread IDs reserve admission synchronously within their dispatcher before waiting. At most one caller may start a CLI process on that thread; the reservation lasts through original process close and admitted identity binding. Cancelled reservations remain ordered behind their predecessor until it releases, so later callers cannot bypass a live owner. Synchronous spawn failure also releases the reservation, and different thread IDs remain independent. This does not serialize whole business review rounds, unknown new thread creation or other dispatcher processes.
CLI cancel(workflowId), cancelSubmission(workflowId, submissionId) and dispatcher stop find calls awaiting admission before they own a child. Each pending call has its own cancellation source combined with the caller's AbortSignal; the first reason wins. Matching queued calls reject without killing another owner or peer. Original active children retain their existing cancellation and binding cleanup; cancelled pending calls cannot start later.
Cancellation also remains registered throughout each CLI transient retry operation, including backoff after its previous child has exited. Matching workflow/submission cancellation aborts that wait and prevents another model process; unrelated submissions remain independent. Dispatcher stop waits for owned retry operations to unwind, and overlapping cancellation paths terminate each original child once. New explicit calls can reuse the same identity after cancellation. See the retry cancellation research.
After a captured original process tree is signaled, its exit-confirmation polling grace uses monotonic elapsed time. System clock changes cannot extend or shorten that grace. Cleanup still needs an independent original-instance exit proof; unavailable evidence remains unconfirmed. Native process queries and termination keep their separate bounds, so the polling grace is not a deadline for the entire teardown. See the cleanup clock research.
Before idle or permanent retirement closes its original native child, an unavailable process census permits one more read only while that same child remains live and owned by the SDK. Both attempts retain the census's existing 4-second bound. Repeated unavailability or a retired owner keeps the original cleanup unconfirmed; it never backfills missing old evidence after closure or starts a replacement backend. See the retirement census investigation.
Failed original retirement, fork or lost-process cleanup appends a compact census summary and exposes frozen cleanupDiagnostics: fixed observation codes, elapsed milliseconds, a numeric exit code and whitelisted errno/signals. Lost-process refresh distinguishes incomplete original/current identities, unavailable capture, a birth-domain mismatch and conflicting descendant identity. refreshed means the evidence was refreshed; independent whole-tree exit confirmation is still required. query_terminated does not alone prove timeout or exit. Raw errors, commands, environment, stdout and stderr are excluded. Diagnostics cannot authorize continuation or change the original cleanup barrier. See the census diagnostic record and lost-process refresh record.
Fork creation and conversion cleanup failures carry the same bounded census summary. The original fork barrier, child identity and no-replacement rule remain unchanged; only the diagnostic evidence is shared.
Lost-process cleanup retains its original startup census observations as a frozen prefix, followed by only the current refresh attempt. Repeated cleanup does not accumulate prior attempts. If the original launcher retires before startup capture returns, owner_retired records that observation while the returned process evidence remains PID-only; captured late identities are discarded. The same prefix appears on that startup retirement barrier. Cancellation alone is not evidence of owner retirement. See the startup census record.
An ephemeral Reviewer whose DSH binding callback temporarily fails remains retained on the same thread. The next request retries the binding callback before starting a model turn, so a transient local persistence error cannot spend a model request or create a replacement Reviewer. Durable/non-ephemeral binding failures keep their terminal cleanup behavior. See the Reviewer binding recovery record.
If cancellation, lease loss, stop, or original-thread loss makes that retention ineligible while the binding callback is still pending, the claimed Reviewer is released immediately. This prevents a leaked subscription or App Server pin when no recoverable record can be published.
Complete public CLI calls retain the same cancellation ownership across readable-output capture and result callbacks. An admitted callback finishes before the call or dispatcher stop settles; cancellation then blocks the next model stage and successful result delivery. Cached input also enters this scope before capture, so a pre-aborted request performs no capture or model work. Callback failures keep their original cause. See the pipeline cancellation research.
When an admitted CLI operation passes its cancelled signal to the next stage after dispatcher stop, it reports that signal's original reason. The retained signal must belong to the same workflow/submission still registered in this dispatcher. New stopped requests and displaced identities retain the stopped admission error; no cancelled handoff starts a model. Non-Error caller reasons retain the existing codex CLI audit aborted fallback. See the first-cause handoff research.
For redirected Windows verification logs, set $env:DSH_WORKFLOW_TEST_REPORTER = "tap" before pnpm verify or pnpm release:check. TAP includes ASCII summary lines such as # tests 1448 and # fail 0, which avoid console damage to the spec reporter's Unicode symbols. The setting accepts spec or tap; unset or empty uses Node's default. Explicit --test-reporter flags, including multiple reporters and their destinations, take precedence. Test selection, concurrency and exit codes are unchanged. Always inspect the actual exit code and completed summary; reporter output can vary across Node versions and is not a replacement for process completion evidence.
Shared Reviewer test fixtures accept the test's t.signal for their phase waits and register t.after cleanup. Cancellation leaves a held wait with the original reason; cleanup joins one disposal and stops the owned manager before waiting for its callbacks. The existing case limits remain unchanged, and a timed-out test remains cancelled. This prevents a failed local fixture from keeping a verification worker alive; it does not classify real provider failures or change workflow recovery. See the test cancellation research.
Manager lease heartbeats contain exceptions from SQLite renewal. A failed renewal leaves ownership unconfirmed, so that heartbeat stops and invokes its existing loss callback once. Active review cleanup follows the original workflow's cancellation path; no replacement model request is started. Successful renewal and unrelated leases remain independent. Explicit renew() and release() still report their own storage failures to callers. See the lease renewal failure research.
Shared CLI failure summaries retain the original head and tail within 2,048 UTF-8 bytes, including the omission marker. The marker counts removed source bytes, and truncation preserves complete Unicode code points. Workflow best-effort failures use the same rule with a 200-byte summary. When a custom budget cannot fit a marker, only a complete prefix is retained. Classification still uses the full original error. See the diagnostic byte-budget research.
Actual App Server exit immediately invalidates the original thread. Its bounded stderr tail can still receive buffered original bytes until stderr ends or a separate 100ms diagnostic deadline expires. An open inherited pipe reports stderr diagnostic stream did not finish before exit deadline; this deadline does not prove descendants exited. Successor admission and stop join the original failure settlement before cleanup. Existing RPC/turn timers stay armed, and retired bytes cannot enter successor errors. See the exit/stream research.
Validation failures in parsed input/turn frames are isolated from the host when they belong to one registered original turn. The first error is terminal: it interrupts that original pair, preserving its thread owner and cleanup handle until ACK or original-process exit. Failed ACKs wait for cleanup; resume sends no model continuation. Frames without a unique owned registration or pending request are consumed without selecting another turn or discarding pending receipts. Non-JSON noise and unknown notifications keep their existing compatibility behavior.
If a scoped protocol error arrives before the original turn/start or review/start reply, the SDK retains it against the unique original pending request. Only the actual reply can confirm its thread/turn pair, including a detached review's destination. The earliest matching error then enters the same terminal cleanup path, ahead of successful output; foreign hints cannot suppress it. Unscoped or ambiguous frames cannot select a pending writer. A reply that never arrives or cannot be validated keeps the original operation unconfirmed; message ids never authorize a fabricated cleanup handle. Caller cancellation preserves late-reply reconciliation rather than declaring the remote operation ended.
Unconfirmed cleanup captures original process identities once: Windows uses CIM creation time in canonical FILETIME units, and Linux uses boot ID plus /proc start ticks. Windows capture excludes a stale parent-PID link when the child's valid creation time predates the current parent instance; an unrelated older process cannot enter the original tree through a reused parent PID. Missing or incomparable birth metadata keeps the conservative capture rule. Later exit checks distinguish a new instance at a reused PID from the captured original. Missing identities or unavailable metadata keep cleanup unconfirmed; legacy PID lists can still confirm when every captured PID is absent. These checks release original resources and do not restore lost ephemeral threads. Other platforms retain the conservative legacy behavior.
0.1.7-rc.2 (the development and type-check baseline; the declared peer range also accepts 0.2.0-rc.2 and 0.2.1-rc.2)^22.19.0 or >=24pnpm doctor)The plugin is developed and type-checked against DSH 0.1.7-rc.2 with cordis 4.0.4; its declared peer range accepts DSH 0.1.7-rc.2, 0.2.0-rc.2, and 0.2.1-rc.2. Run pnpm host:check against the operator's installed core packages (isolated temp storage, no live profile/session/model) before treating a host as verified; the check also asks the host's own evaluatePluginCompatibility to judge the declared range.
Two things matter when the host moves:
dsh checks every @deepseek-ai/dsh* peer against the running version and asks for an exact-version exemption when one is unsatisfied (dsh plugin allow-version). Its documented semantics let prereleases participate in ranges, so a range is not judged by plain npm semver — that is why the gate reuses the host's evaluator instead of re-implementing it. pnpm doctor reports the same verdict (SKIPPED, never passed, when no DSH profile exists), so pnpm release:check inherits it.pnpm install (plus pnpm build) after a host upgrade; upgrading dsh alone does not update a linked plugin's node_modules, and the plugin's src is type-checked against the versions pinned in its own devDependencies.Run pnpm host:check to load and unload the built plugin using the actual DSH
core packages installed under $DSH_HOME/profiles. It checks that all nine
tools exist when activation resolves and disappear on unload, using temporary
storage and an empty agent registry. It does not restart the live profile or
perform a model call. For another installation, set
DSH_CODEX_HOST_PACKAGE_JSON to its @deepseek-ai/dsh/package.json path.
After the checks, load the new build through the normal operator-controlled
DSH profile restart; existing workflow storage requires no migration.
dsh plugin --profile web add dsh-codex-workflow
Restart the DSH web profile after installation. For source installs during development, pass the local project directory to the same command.
In-process transient retry budgets use a shared monotonic elapsed clock for control RPCs, readable Planner/Reviewer turns and CLI attempts. Clock corrections preserve the original budget; caller cancellation keeps its original cause, and exhausted retries keep their last actual outcome. See the clock regression tests and research evidence. The allowance is measured separately for each retry operation.
pnpm install
pnpm verify
pnpm host:check
pnpm host:recovery:check
pnpm doctor
pnpm test runs every matching suite with four independent test-file processes by default. Set DSH_WORKFLOW_TEST_CONCURRENCY to an integer from 1 to 4 when memory is limited; this also applies to verify and release:check. Concurrency inside each scenario and its timeouts remain unchanged. For a machine with more resources, an explicit command is pnpm exec tsx --test --test-concurrency=8 test/*.test.ts.
$env:DSH_WORKFLOW_TEST_CONCURRENCY = "2"
pnpm verify
The pre-1.1.0 manual harnesses (pnpm lifecycle:accept, pnpm review-only:accept) were REMOVED: they drove the legacy CLI-visible wiring, predated the dedicated-Reviewer and App-Server-visible layouts, and could no longer be trusted as acceptance evidence. The gates that replaced them are the automated ones above plus pnpm host:check and an independent review round.
pnpm host:recovery:check exercises the installed DSH ToolRuntime, AgentRegistry, SessionStore, SubagentRuntime, session projections and JSONL persistence in isolated storage. Eleven scripted local scenarios cover Planner/Reviewer failure recovery and cleanup, native and completed-question clarification answers/cancellation, a submitted native answer aborted before interruption confirmation, and ordinary Planner/Reviewer waits with unconfirmed interruption. They assert the original child/thread, retained settings, balanced pending tools and one-time cleanup. After child disposal, an independent persistence instance reads the original parent/child journals and checks their complete events, messages and catalog identity (persistedColdReplay: true). The script does not run a real App Server, call a real model or validate the browser UI.
The browser selects the latest review's native child in waiting and completed states, so the summary tab does not hide continuation messages after activeReview clears. test/reviewer-sidebar.test.ts covers that transition, completed-history replay, genuine new review rounds and the legacy childless fallback. See DSH same-session acceptance for the separate browser and real-provider verification boundaries.
The review summary labels paused, failed, cancelled and stale progress from the declared process state; existing history alone does not indicate completion. Cards count review attempts, including recovery attempts, rather than implying a new business review round. Planner clarification and failure states also have distinct headings. A paused heading does not establish continuation eligibility: query codex_workflow_status before resuming.
Planner events also carry an optional process state: a recoverable wait displays “规划已暂停”, same-thread continuation publishes running, and a ready plan publishes completed. Terminal failures awaiting cleanup retain their failure heading. The projection cache uses state version 2 to refold original logs; legacy logs without the optional field remain readable and do not invent a pause or a resumable thread.
While a sidebar projection is loading, the summary shows “正在加载工作流状态” using a stable immutable snapshot. Once the original store supplies data, its subscription renders that data; a loaded empty workflow still shows “等待审查”. This loading guard does not start agents or recover unavailable model threads.
Sidebar unloading retires its scanner before releasing listeners and retry timers. Already captured notifications cannot reopen a tab or subscribe to another session after unload; session switches release the previous projection observer once. test/sidebar-disposal.test.ts covers retirement, repeated disposal and normal live discovery. The adopted lifecycle mechanism and evidence are documented in the research note.
Sidebar auto-opening gives each original child or summary target one initial attempt and up to eight retries within its original 15-second retry window. After that allowance is exhausted, ordinary projection notifications keep it exhausted. A distinct target has a separate allowance; explicit Host sidebar actions remain available. See the sidebar retry tests and mechanism comparison.
For verification while DSH uses this checkout, run pnpm verify:isolated with development dependencies installed. It runs verify, release, installed-host and recovery checks in an independent snapshot, then checks the original Git diff. Evidence is retained in a sibling .codex-workflow-verification/isolated-* directory; summary.json, logs/ and artifacts.json identify the tested input and output. Original source or canonical build content/metadata drift rejects the result. Development pnpm build can update a linked host through HMR; isolated verification returns an artifact path for a separate controlled delivery.
Isolated test gates also retain logs/verify.tests.jsonl and logs/release-check.tests.jsonl from a companion Node reporter. Each receipt records its original run id, source directory, Node version, every test file's summary, the cumulative summary and stream completion. The gate requires its actual command exit code, matching file inventory and complete passing results; missing receipts, premature zero exits, skips/TODOs or async warnings reject it. Human reporter flags and their ordered destinations remain available. Receipt files are created exclusively, and independent descendant launchers cannot write to their parent's scoped receipt.
From the Codex task that owns the plan, dispatch it to the live DSH session:
# 1. Find the live DSH session for this workspace
dsh-codex-workflow sessions --cwd $PWD --json
# 2. Dispatch the plan (payload enters through stdin, never arguments)
$payload = '{"task":"实现搜索功能","planMarkdown":"<proposed_plan>…</proposed_plan>","assumptions":[]}'
$payload | dsh-codex-workflow dispatch --cwd $PWD --codex-thread $env:CODEX_THREAD_ID --stdin
The bridge resolves the exact session (explicit --dsh-session wins; otherwise the cwd must match exactly one live session, and ambiguity fails loudly). DSH receives the plan as a plugin relay message and implements it. When done, DSH calls codex_workflow_submit; the plugin validates the exact stored Codex task id read-only, then runs the readable audit in a fresh private Reviewer thread. The originating task is never resumed and never written:
Planner/source Codex task --App Server--> readable plan
private ephemeral Reviewer thread --App Server thread/start + review/start--> readable Markdown review/rewrite
private ephemeral conversion --codex exec --ephemeral --output-schema--> normalization/alignment JSON
Planner and Reviewer threads created by 1.1.7 are ephemeral. Each Planner or Reviewer starts with thread/start { ephemeral: true }, is used by the current NPM Codex CLI App Server process, and is never named or persisted as a new plannerThreadId/reviewerThreadId. Ephemeral ids do not appear in thread/list, so Codex Desktop does not load them or hold their writer locks. A new review round creates a fresh Reviewer; failure recovery within one round keeps its original Reviewer. Legacy ids remain readable for diagnostics only; they are not resumed, renamed or handed off. codex_workflow_resume rejects a lost original process instead of reconstructing its context. The visible turn receives the configured <reviewerModel> and <reviewerEffort>; a blank model uses the resolved default.
The DSH session history is human-readable. Visible Planner/Reviewer turns never carry an outputSchema: each review is readable Markdown (VERDICT / FINDINGS with severity, blocking and file:line / TEST GAPS / SUMMARY) in the original task's language. Private conversion uses the same effective model with low effort: Planner and default foreground Reviewer conversion run on their original ephemeral App Server thread; authority checks use private seeds or the explicitly selected non-persistent CLI transport. These operations never create a durable Desktop task or persist internal ids into plannerThreadId, reviewerThreadId, or codexThreadId.
Every review turn carries its own full context — Git included. DSH-led, bridge callback, first review and re-review all reuse the existing review prompt generator. The review/start custom target (and the App Server turn on the callback path) carries workflow identity, original task, approved plan, PREVIOUS APPLIED REVIEW, current fix summary, implementation summary, changed files, test results, bounded evidence, review scope, and the item-by-item authority gate. Git workspaces require independent read-only git status/git diff; non-Git workspaces use the same contract without inventing a different Reviewer policy.
Review authority alignment (1.0.10). After the visible review is normalized into the structured verdict, an invisible private conversion turn (same read-only conversion machinery, low effort; in-place for ephemeral sources) checks every finding/test gap against an authority hierarchy: 1. a REPRODUCIBLE critical/high correctness/security/data-corruption defect (must carry concrete file:line + failing-scenario evidence) > 2. the ORIGINAL TASK and its explicit constraints (file scope, exact test counts, dependency limits, acceptance method) > 3. the APPROVED PLAN > 4. the previously applied findings and the current fix summary > 5. generic quality suggestions. Ordinary scope/test-count/verification-method conflicts resolve in the plan's favor: automated tests, STATIC CHECKS and REAL COMMAND verification are all formal evidence of a requirement — the Reviewer may no longer demand automated tests for behavior the task/plan verifies by real commands, and may not demand changes that exceed the task/plan's explicit bounds (a level-1 exception with reproducible evidence is the only override). The Planner contract mirrors this: it must never strengthen "tests cover A and B" into "exactly two tests" unless the user explicitly limited the count.
The visible review is contract-checked before it reaches DSH. The readable Markdown is streamed into a native DSH child Session and the browser opens its subagentchat right-sidebar resource. The owning parent Session still receives namespaced dsh-codex-workflow/* workflow events for status and recovery. A structured ReviewResult envelope is never accepted as visible output; a display violation runs one corrective private turn. Nothing on the completion path calls the Desktop opener or any navigation/refresh API.
Planner and Reviewer model instructions identify the current DSH child Session and Codex Review right sidebar as the readable output destination. The foreground native review target, Reviewer developer instructions and Bridge callback carry this same display contract. The hidden App Server thread is the execution transport; its readable output remains Markdown in the task's language, while structured conversion stays in its separate internal stage.
The browser client retries a failed native-sidebar open for a bounded window and never falls back to the custom Codex Review tab while a child session exists. When no child session is available, it uses the public sidebarRight.openTab page API for the diagnostic fallback. Closing an already opened tab is treated as a user choice and does not repeatedly reopen that same child during the current page lifetime; a refresh or a later child session creates a new opening opportunity.
Each new review round has its own native child session, so multiple rounds may leave multiple subagent tabs in the right-sidebar history. A recoverable model failure stays inside the current child session and current App Server Reviewer thread; it does not create another child or replay the work in a fresh thread. The plugin does not close older tabs because the public sidebar API does not expose a reliable content-address lookup or the tab id returned by openResource.
Conflicts never cost the user a review cycle and never ask DSH to change code. A conflict does not overwrite latestReview, increment reviewCycles, enter fixing, or send a fix instruction. One reconciliation turn may rewrite the complete verdict on the same ephemeral Reviewer; its prompt contains an explicit preservation manifest for every non-conflicting finding/test gap. Deterministic multiset checks reject deletion, field changes, duplicate-count drift and unrelated additions before re-alignment. A successful correction applies as one business cycle; two consecutive unresolved contract conflicts block without consuming a cycle.
Before a new App Server review round, the plugin uses a newly created ephemeral Reviewer already held by that App Server client. If the current model turn fails with a recoverable run error, the same Reviewer is continued once and can remain waiting for codex_workflow_resume; it is never replaced during that round. The plugin never resumes a legacy Desktop task, never names the ephemeral thread and never invokes the Desktop opener. Legacy opener fields remain compatible but audit records keep desktopOpenState: "disabled".
Desktop cannot lock a new 1.1.7 review. Because plugin-created Planner and Reviewer threads are ephemeral and omitted from thread/list, opening an old Desktop task cannot block a new review. Control RPC failures remain bounded and retryable, but no retry resumes or reuses a legacy Desktop task.
Ephemeral threads are released with the App Server process lifecycle. Cancellation and teardown interrupt the exact active turn or private conversion turn before the client stops; no Desktop task is reopened or refreshed.
The model directory, default model and catalog-derived reasoning effort are cached for the current App Server process. Repeated reads in that process keep using its cache. Concurrent catalog observers in one generation share one metadata RPC, while each caller keeps its own cancellation boundary. Public model-list results are detached snapshots, so a caller cannot mutate the generation's cached model choices. After an actual process exit or idle shutdown, a metadata request prepares the new server and reads its own directory; new independent work uses that server's defaults. Explicit model/effort overrides retain priority. Cache reset invalidates the old namespace, and checks after asynchronous catalog/default/Plan-mode lookups prevent late old results from repopulating the new cache.
Before opening an existing coordination database for schema migration, the store creates a .pre-schema-v2.bak copy. A failed backup now stops initialization and preserves the original database for manual recovery; the error is not downgraded into a best-effort warning.
An admitted Planner/Reviewer keeps its saved model, effort and original thread owner through continuation. Refreshing metadata does not retarget that run to a new process or create another workflow/child. Initialization must succeed before metadata or thread operations are admitted: a rejected or malformed initialize response preserves its primary cause and cleans only its failed original child. A later independent SDK request can retry initialization; this does not reconstruct an interrupted Planner. See the model catalog iteration and fixed cache research.
DSH 0.1.7 replaced the shared catch-all plugin message source with a merge-extensible sum type: MessageSourceMap is augmented by each producer in its own module, and MessageSource.kind answers who produced this while form answers what kind of thing it is (the two axes are deliberately independent). There is no plugin kind to borrow any more.
The plugin therefore declares its own kind in src/message-source.ts — exactly the way first-party producers such as dsh-agent-instructions do — and builds every injected message from it (relaySource() for a plan handoff / verdict / outcome, noticeSource(summary) for a one-off notice). A cast would have compiled, but it would have hidden the contract from the compiler and left the harness unable to attribute this plugin's context to a declared producer.
Capacity and throttling problems are temporary, so they never end a workflow any more. One shared policy (src/transient-retry.ts) classifies server_overloaded / "Selected model is at capacity" / 429 / usage limit / 502-504 / dropped streams / a thread writer held by another process, and retries the operation with exponential backoff plus jitter inside transientRetryBudgetMs (default 20 minutes). It covers every VISIBLE App Server turn — planner start/continue, the completion turn, the DSH-led review/start review turn, the display rewrite and reconciliation — plus the callback path's visible turn (which stays retryable_busy for the bounded callback retry and the periodic recoverCallbacks sweep) and the CLI audit's ephemeral conversions (normalize, align, reconcile).
Terminal conditions are never retried: a thread that does not exist, schema or display-contract violations, approvals, and any cancellation. A visible turn is retried only when it failed WITHOUT producing visible output, so a retry can never duplicate a plan, an audit or a rewrite, and a pending backoff is interrupted immediately by cancel/teardown. When the budget is spent the outcome stays RETRYABLE (the workflow records the reason and the user can run it again) — it is never reported as a terminal failure. Long diagnostics keep head AND tail, so the persisted error always shows the real reason.
After each backoff wait, control, visible-turn and CLI retries recheck cancellation and the original elapsed-time deadline. A late wake-up at or after the deadline cannot start another attempt. Control retries report their last original error and actual attempt count; visible and CLI loops return their last transient outcome. Zero retry budget still permits the initial operation. This admission bound does not interrupt an already started operation or reset the workflow's separate one-shot continuation budget. See the retry wake-up research.
Tool deadlines reserve each serial turn/control RPC's retry allowance, rather than assuming only two retry budgets. Reviews also reserve the separately configured CLI conversion/alignment timeout when that transport is selected or used as fallback. These are conservative host deadlines; they do not add model retries. Multiplication overflow saturates the calculation instead of dropping an allowance. A local Codex turn timed out: <turnId> uses the one-continuation policy below, outside the long transient backoff loop; a control-RPC timeout does not qualify as a model timeout.
The actual complete-operation budget must fit the DSH host's positive finite timer range, up to 2147483647 ms. Activation validates the composed budgets before registering projections, opening storage or registering tools; unsupported combinations report the tool and timeout settings to adjust. Budgets are never shortened to make them fit. A large per-turn setting can remain supported when its other budgets are smaller; there is no arbitrary static per-turn cap.
A running review is observable from the durable record, not from model output. Each attempt persists processState (running / waiting / completed / failed / cancelled / stale), reviewStep (review_native_turn, review_readback, review_display_rewrite, review_conversion, review_alignment, review_reconciliation, review_finalizing), reviewAttempt, reviewStartedAt, lastProgressAt, reviewElapsedMs, activeTurnId and a short lastProgressMessage — safe phase diagnostics only, never model text or raw JSONL. codex_workflow_status returns them with the record.
An independent DSH-owned timer (reviewHeartbeatMs) keeps the heartbeat fresh while a review runs, so a wedged review is visible even when the model produces nothing; status reports stale once the last heartbeat is older than reviewStaleMs, and completion, failure, cancellation and teardown stop the timer and record the terminal state.
Persisted recovery markers are validated on read, write and migration. Present stage/attempt/boolean/checkpoint fields must have their declared JSON types and supported values; arrays are never coerced into valid selectors. An invalid marker reports the workflow id and fails before status advertises continuation or a mutation commits. Legacy interruption-only records remain readable, but never recreate a process-local thread.
Callback recovery uses the same recoverable-error classifier for immediate continuation and later retained-thread recovery. Schema, permission, authentication, invalid-thread, display-contract and concrete control failures cannot become continuations merely because a diagnostic also mentions a timeout, rate limit or active writer. A genuine model timeout or generic model failure can continue once; a second failure retains the original Reviewer for a short manual/background continuation.
Failed and cancelled child turns retain text already streamed in their current segment, including text buffered behind a native tool, and append the failure diagnostic. Continuation adds a new turn to the same child. Retained partial output is inspection history; it does not become an accepted plan/verdict or change continuation eligibility. Successful authoritative output still determines the final message.
App Server replacement snapshots update only the matching kind/item in the current native message segment. The child retires the superseded live display attempt and republishes the corrected snapshot on the same turn and step; it does not start a model request or another child. Repeated plan snapshots do not concatenate old states, and committed messages before a tool boundary remain history. Ordinary deltas, peer items and later continuation turns keep their own contents.
Terminal registration, permission, Schema or private-input failures cannot authorize model continuation. If their original turn has not confirmed interruption, retryState.awaitingCleanup retains the primary error and exact process-local cleanup capability. Status reports interruption_unconfirmed; resume retries only original-turn cleanup, then terminates with that same error. Cancel/stop retain the child, subscription and pin while ACK remains unconfirmed. Whole-plugin shutdown closes its SDK and reconciles pending cleanup before closing storage. An independent SQL guard prevents another Manager from starting a replacement, including after cancellation, scope change or guard TTL expiry; only exact confirmation or verified exit of every captured original process releases it. See the terminal cleanup iteration.
Successful cleanup confirmation belongs to its original execution. Process-local receipts use the original retained run/turn owner; persisted receipts require the exact workflow, session, token and lease epoch. Reused thread/turn text, the same PID, an expired TTL or a historical confirmed tombstone cannot authorize a new execution. A new terminal failure retains its own guard before publishing its waiting marker; recovery retries its own ACK without calling the model. A late old cleanup cannot remove a successor's marker, unsubscribe its reused address or release its pin. See the confirmation generation iteration.
The RPC itself also requires the original thread registration and actual App Server process. An opaque owner is captured before asynchronous adoption and checked before model requests, native answers, interruption and subscription cleanup, including after awaits. A replacement process or a new registration with the same textual thread/turn IDs cannot receive an old recovery request; status and resume report that the original thread is unavailable. Private alignment/reconciliation seeds keep their own owners. Metering reads, bindings and releases preserve the corresponding original source instead of consuming or retiring a successor's counters. See the transport owner iteration.
Planner recovery retains validated readable planning, structured conversion and controlled completion in its original process. A failed conversion resumes only conversion; a successful completion is never replayed because its later conversion failed. Automatic continuation is limited to one for the entire planning business flow, including clarification and later manual resumes. Private retries send a short continuation with the enforced schema, without resending the complete task or cached plan. Successful responses are captured before caller-abort checks; a finalizing resume can finish from the validated result with zero new model calls. Schema/display errors stay terminal, and raw Markdown never authorizes execution. The same child's final message shows the actual completed plan. See the Planner stage iteration.
When caller interruption follows an already received native clarification, the original request remains in waiting_input; resume sends no new model turn and the user's answers use continue on that exact request. If submitted answers already produced a completed plan before interruption, they are not submitted again: recovery only converts the saved reply. Waiting checkpoints require their persisted effective settings to match; deleting a resolved model cannot turn a current run into a legacy fallback.
When a Planner or Reviewer turn fails without usable output because of a recoverable model/runtime error, the plugin sends one short continuation instruction on the same App Server thread and keeps the same DSH child conversation. If that continuation also fails, the record enters processState: "waiting" with a retryState. A tool-call interruption after thread creation also retains that waiting handle. Explicit workflow cancellation and plugin teardown end the lifecycle; invalid thread ids, permissions and schema/display violations are not automatically continued.
Foreground reviews retain completed stages as process-local checkpoints. Once the readable review, conversion or one-time authority correction succeeds, recovery reuses its result and runs only the failed stage on the original Reviewer/child. The entire business round has one automatic continuation budget, shared with native review failures; manual resume does not reset it. retryState.reviewCheckpoint stores only the stage, never the cached output or an ephemeral thread ID. A lost checkpoint is reported as unavailable rather than reconstructed from current input.
Private alignment/reconciliation interruption is cleaned through its own exact pair and original retained execution owner before another model starts. Its receipt remains reusable for that original owner; identical workflow/thread/turn text on a different execution cannot inherit it. An empty runtime table is not confirmation. Losing the original Reviewer process keeps the checkpoint waiting and starts no replacement process. Scope, approved plan, previous review, saved settings and original evidence are checked before recovery and after asynchronous stages. Temporarily unavailable final evidence can be restored and verified with zero model calls; a changed fingerprint invalidates the old result. Registered permission/Schema failures and unresolved authority conflicts require verified original-scope changes or an explicitly requested independent round before another review. See the foreground stage checkpoint iteration.
For an ordinary model wait that times out or aborts, the client awaits interruption of that original turn before ending the wait. A failed confirmation returns a typed, process-local marker instead of entering model backoff or automatic continuation. Foreground Planner/Reviewer and Bridge retry retain the original thread/child/pin and confirm cleanup before another model request; status reports interruption_unconfirmed for foreground runs. A later terminal notification can also confirm completion. Cleanup of an exited/stopped process sends no request to a replacement process. See the model interruption iteration.
For foreground Reviewers, loss of the launcher during process-tree observation still preserves a durable unknown interruption barrier. It does not become an ordinary retryable failure that can start another Reviewer. If the original turn's interruption is confirmed but the caller aborts before continuation starts, the confirmation is saved under the original review lease and scope; the retained waiting handle does not advertise a pending interruption that already ended.
Private alignment and reconciliation seeds belong to their creating scope, which holds its own process pin during creation, retry backoff, conversions and rechecks, then releases its subscription and pin. This also protects Bridge auxiliary work after its original Reviewer has finished. If interruption remains unconfirmed, the original private thread/turn and that pin stay in a separate cleanup table. Cancel/stop retries that exact pair before release. A new foreground review or background callback cannot run while this private turn is unconfirmed; it is never presented as the original visible Reviewer's continuation. thread/unsubscribe alone does not prove model termination. See the review entry and resource iteration.
For DSH-led planning and foreground review, call codex_workflow_resume with { "instruction": "继续原任务" }; workflowId is optional and defaults to the active workflow owned by the current DSH session. This is a DSH tool, not a shell/bridge CLI command. A normal model continuation appends a display turn to the original child and sends only the short continuation instruction to its original Codex thread. Pending receipt inspection sends no model request; adopting its genuine reply observes the original model turn, and terminal recovery performs only confirmed cleanup. Concurrent requests with the same instruction share one recovery execution; a different instruction is rejected while it is running. Bridge reviews continue through their original submission's background retry, including when status reports an available callback; do not call foreground resume or resubmit unchanged material. Another session cannot join the operation. stop() awaits admitted continuations and their cleanup. A plugin restart cannot recover an ephemeral thread and never silently creates a replacement.
If start was interrupted before returning an ID, first call codex_workflow_status with {}. Both status and foreground resume can resolve the current session's original workflow without a supplied ID; no active alone does not prove that a startup record never existed. Follow the returned continuation capability: wait for running; for start_receipt_pending, wait for the genuine original reply or inspect that retained operation without another model start; resume an available foreground handle; or retry only original-turn cleanup for interruption_unconfirmed. start_receipt_invalid requires confirmed original cleanup and never authorizes a fabricated handle. Bridge receipts remain with their original background submission. An admitted creation response can still retain the original thread/child after caller abort; caller interruption is not process restart. The once-per-task start guidance prevents duplicates and is not a permanent quota after a failed startup. It does not authorize silently replacing an interrupted workflow or finishing its plan/review in the parent chat. See the tested creation boundary, startup diagnostics and tool contract.
Planner runs save their model and requested effort before asynchronous creation. The client-resolved Plan-mode model is captured even when the turn fails or is interrupted. Automatic retries, manual continuation and visible completion reuse that run's saved choices if global settings change while it waits. Private structured conversions retain their separate low policy. Legacy records missing choices keep the existing configured fallback; this does not reconstruct unknown historical settings or provider-side effort. See the Planner settings iteration.
Planner results also retain their original workflow/session, cwd, task and creation identity. Automatic continuation, conversion, completion and every final SQLite mutation recheck that scope and the expected phase. Late success or failure cannot overwrite a displaced workflow or inject a plan. Waiting and clarification handles keep the captured identity; changing task/cwd does not retarget their original thread. Status reports identity_changed and preserves the handle. Missing original identity is handle_unavailable; neither case creates a replacement. Terminal cleanup clears the active cancellation target, including a native input commit lost to cancellation. See the Planner ownership iteration.
Clarification also retains the original Planner child, thread and process pin. For phase: "waiting_input", use codex_workflow_continue with the question IDs and answer arrays. Native pending questions receive a JSON-RPC reply to their original request; completed question turns receive a short answer followup on that same thread. Concurrent identical answers share one execution, and conflicting answers are rejected. If answering fails, the short submitted answers remain in the process-local recovery handle for resume. A lost or restarted process cannot be replaced silently, including for legacy question records. See the clarification lifecycle iteration.
If the native answer was submitted before the caller aborts, the plugin confirms interruption of that original turn before offering another model turn. An unconfirmed interruption keeps the original thread, child, answers and pin, with continuation.reason: "interruption_unconfirmed". codex_workflow_resume may retry cleanup in this state; it starts no model turn until confirmation and never replies to the answered request again. Cancel/stop interrupt the retained original pair before release. Unsubmitted answers still use the separate original-request restore path. See the submitted-answer iteration.
Native child history advertises each actual tool item in an assistant tool-call block before its matching tool/call and tool/result events. Stream steps close after their pending tools settle, supporting V4 persistence and sidebar replay across continuation. This fixes newly written events; existing corrupt session logs are not repaired or rewritten automatically. Local evidence and remaining runtime acceptance are recorded in the sixth runtime validation.
An interrupted Reviewer can continue only while its workspace evidence is unchanged. If files changed, start a new review round so a previous review cannot approve different content. Research and local iteration evidence: GitHub mechanisms and session recovery iteration.
Concurrent foreground reviews of the same workflow and identical implementation input share one execution. Different input is rejected while the review is admitted; another DSH session cannot join it. A workflow-scoped SQLite lease prevents separate managers from creating competing Reviewers, and phase/plan/review-cycle limits are checked before any child or model turn starts. Review writes validate the lease inside their SQLite update transaction, so a former owner cannot apply a verdict or park a continuation after takeover. Cancel and stop retain authority over the owning workflow and await admitted work.
If foreground child creation or review-start persistence fails before a model starts, the owned workflow returns to executing (or its previous fixing phase) with the startup error. Query codex_workflow_status and explicitly retry codex_workflow_review using that workflow ID. No review cycle or model-thread recovery handle is invented. If an existing waiting Reviewer was claimed but continuation has not started, a failed startup preserves its original waiting record, child, thread and pin for another resume. Cancellation returns the current cancelled record; stop awaits late child disposal. Child adoption and review-start transactions recheck the original session, workspace, task and attempt, so a displaced startup cannot run a model or change the replacement record. See the foreground startup iteration.
Review-only creation saves an executing record before admission; only the actual review observation enters reviewing. A session creation lease serializes the final active-workflow check and save across managers, preventing duplicate children while an original workflow is active. An abort between save and admission leaves the same workflow addressable for explicit review, with a startup diagnostic when its review lease is free. A denied caller cannot modify the competing lease owner's record or release its lease. The original scope is checked after creation, inside admission and before child startup; cancellation returns the current cancelled record. See the creation and admission iteration.
Parent turn-stop guidance follows the current workflow action. A waiting Planner/Reviewer is directed to status and original-thread resume, subject to the returned capability and interruption confirmation; the parent cannot substitute its own plan/review. A review-only workflow awaiting review uses the existing implementation without an invented implementation step. Changed rows, cancelled/stopped managers and running original operations receive no duplicate nudge. Clarification, review decisions and active Bridge submissions keep their existing waiting semantics. This hook observes state and sends guidance; it does not start a model, mutate durable records or prove that a retained thread can continue. See the action routing iteration.
Waiting Reviewer handles also capture the original workflow/session, workspace, task, creation time, mode, model/effort and waiting attempt. Status, resume and changed-workspace review preparation verify that captured identity before using the thread; drift reports identity_changed without consuming its handle or changing the record. A handle lacking original identity reports handle_unavailable; it is never reconstructed from the latest row. Interruption cleanup and claimed startup recheck identity after asynchronous work. A valid continuation can advance the attempt while retaining the original thread/child, and captures the next waiting identity if it fails again. See the Reviewer recovery identity iteration.
Each foreground review also snapshots its native request's effective model and effort, including a deliberately omitted model when no default was resolved. Automatic continuation and manual resume reuse those choices even if global settings change; private conversion/alignment keeps its existing low-effort policy and uses the captured model. Missing original settings are unavailable, rather than reconstructed from new defaults. An explicit changed-workspace business round selects fresh settings when the workflow has no saved override. Bridge final progress and cleanup notes validate the original submission identity and actual lease inside their update transactions; revoked callbacks cannot revise a successor's completed progress or publish a duplicate review-end event. See the settings and final progress iteration.
Bridge progress guards also travel into nested alignment/reconciliation helpers. Private creation, turn registration, reconciliation retry and later conversion recheck ownership; a revoked alignment cannot publish CLI fallback progress or start that fallback model. Callback progress/continuation and visible text verify a synchronous current-record snapshot and actual lease. Batched live entries repeat the check when emitted, so data accepted before takeover cannot appear after the successor completes. A legitimately committed terminal phase retains its own finalization authority; it does not relax another owner's checks. See the nested progress iteration.
Review heartbeats retain their original timer registration. Late callback cleanup, interval errors and rejected finalization clear only that registration, preserving a successor's running heartbeat. Startup checks current ownership before replacing a timer; stale probes reject changed revisions without writing progress. Explicit workflow cancellation and plugin stop still clear their current timers. See the heartbeat ownership iteration.
Background App Server reviews also retain an admitted consumer for their shared operation and native child. A valid local lease successor adopts that same active or waiting Reviewer after request checks; old wait cancellation and cleanup cannot interrupt its model or close its child. Progress, display rewrites and usage hooks follow the current consumer. A retired original or a fresh process with only an old ephemeral record reports unavailable without rebuilding the review. Explicit cancellation/plugin stop still own full cleanup; legacy CLI callback adapters keep their separate recovery contract. See the shared Reviewer consumer iteration.
When a background review loses its original thread or completed result, status, turn-stop guidance and failure notices report that specific unavailable state. A default codex_workflow_submit cannot repeat unchanged material as recovery. A verified change in the original review scope permits a new business round; changing file selection, summary or test wording alone does not. For unchanged evidence, use newReviewRound: true only after the user explicitly requests a new independent review. This starts a new submission and is never original-thread continuation. Startup failure before any Reviewer exists retains its ordinary first-review retry. See the unavailable review actions iteration.
Foreground codex_workflow_review follows the same new-round boundary on the existing workflow, including workflows originally created by review_only. After process loss, a real change in the original persisted evidence scope permits a new review; changing the submitted file list or prose does not. With unchanged evidence, newReviewRound: true requires the user's explicit request for an independent review. The flag stays out of stored and model review material, but distinguishes concurrent execution requests. New rounds select fresh unsaved settings and consume a cycle only when a verdict is accepted; they do not recover the lost thread. Ordinary resume remains original-thread continuation. Both routes respect the current review lease, and an outstanding original interruption must be confirmed before another model starts. Its durable retryState.interruption marker preserves the original launcher's PID and observed descendant PIDs, never an ephemeral thread ID. Without the original turn handle, that new round requires a complete process-tree observation and OS confirmation that every captured PID no longer exists; a live, inaccessible or unknown process is rejected. The read-only tree query is bounded to four seconds and uses Windows process metadata or Unix ps; failure remains unknown. Missing local maps, an unavailable SDK subscription or launcher exit alone do not prove backend exit. Use review(existingWorkflowId, ...) for later rounds instead of creating another review_only workflow. See the foreground new-round iteration.
Bridge-origin workflows use codex_workflow_submit and their original background review. Calling the foreground codex_workflow_review on such a workflow is rejected before acquiring model or child resources, including before its first submission and while it is sending, retrying or delivering a verdict. Session ownership is checked before this routing diagnostic; the route is rechecked inside the admitted review lease.
Foreground reviews collect evidence again after conversion, authority alignment and reconciliation, before accepting any verdict or contract failure. Changed or unverifiable evidence rejects the old result without consuming a review cycle or sending a repair/completion instruction. The same workflow remains retryable; a new business review requires an explicit request. Non-Git workspaces must provide readable, workspace-contained changedFiles. Git fingerprints include the actual HEAD commit, so switching between different clean commits invalidates the old evidence. The final SQLite update also checks the original owner, attempt and evidence fingerprint. This verifies a filesystem snapshot; it does not lock files against later edits. See the review freshness iteration.
codex_workflow_status also returns continuation: { available, reason? }. Only an owned, waiting workflow with a retained handle in the current App Server process is available for manual continuation. For a waiting Reviewer, status reads workspace evidence using the original review scope: workspace_changed means a new business review round is needed; evidence_unavailable means the original evidence cannot currently be verified. These diagnostics preserve the original handle and saved review state. Other reasons distinguish a running owner, a lost thread, a missing handle, cancellation, teardown and other phases. The probe sends no model RPC; an old durable running record alone is not proof of a live continuation. Availability describes the observation; resume still checks evidence before execution.
The legacy codex_workflow_continue failure-recovery branch delegates to the same admission as resume, including concurrent-call merging, original-thread availability checks and cancellation. Normal clarification answers keep their existing waiting_input path. See the seventh recovery eligibility iteration for regression evidence.
When status reports workspace_changed, an explicit codex_workflow_review request can start a new business round after the original review scope confirms actual evidence changes. Outstanding original interruption is confirmed before the old waiting child, subscription and pin are released once; the same workflow receives a fresh Reviewer. Changing request text alone does not qualify. Unchanged evidence preserves the original continuation path unless the user explicitly requests an independent review through newReviewRound: true. Status and resume do not create a new round. See the explicit review round iteration.
If a tool was interrupted before returning JSON, query codex_workflow_status with {} first. Send a model continuation only when continuation.available is true; running means wait. With interruption_unconfirmed, codex_workflow_resume may retry the original turn's cleanup before continuing. The start tool's "at most once" instruction prevents duplicate workflows, rather than consuming an irreversible quota on failure. Never invent a handle, restart silently or substitute a parent-authored plan/review.
An admitted Planner creation collects its original thread/start response within the existing App Server RPC deadline even after its caller aborts. Status reports the actual in-process startup as running while that response is pending. A returned original ID is retained with the same child for continuation; no model turn runs for the aborted caller. Cancel/stop instead release that original late resource once. A creation error or expired response deadline still cannot supply a recoverable thread. With no active workflow, status may report this session's latest failed Planner startup as diagnostics, including its real workflow ID and error; it does not make a terminal record resumable or select an older task. See the startup response iteration.
no active Codex workflow means the session has no active record; it does not establish that durable failed records are absent. Likewise, a corrupt DSH child history does not establish whether its App Server thread still exists. Use the workflow's continuation diagnostic to determine current-process recovery; sending a generic subagent message is not the workflow continuation route.
Foreground tool returns retain the completed business result and validate its owner against the original calling DSH session. Usage refresh can add only counters from that same owner; it does not replace the result with a newly owned workflow record. Planner cancellation fast returns also validate the captured scope before returning.
When the current original Planner/Reviewer thread has received thread/tokenUsage/updated, status includes tokenUsage with source: "app_server", scope: "current_original_thread", stage, server-reported total and last counters, and an available modelContextWindow. Counters distinguish input, cached input, optional cache writes, output and reasoning output. Cached input and reasoning output are subsets of input and output respectively; do not sum all fields together. Repeated notifications replace snapshots rather than adding cumulative totals twice. Missing notifications or optional counters mean unknown usage, rather than zero.
This live view is the server's cumulative usage for the current original thread, including continuations and any conversions executed on that same thread. Separate forks, CLI conversions/alignment, earlier review rounds and the parent DSH model are excluded. It is not a workflow bill or a measured savings claim; last covers the last model response, which may be only part of a turn. The live view does not expose ephemeral IDs; released/lost threads and plugin teardown discard its cache. Internal conversion turns are never advertised as active original-thread usage. The client retains at most 128 thread snapshots; the separate durable observations below survive cache cleanup.
A retained foreground Reviewer may wait in executing or fixing; its original-thread usage remains readable when the waiting handle is valid. Bridge submissions use the dispatcher's claimed original Reviewer for this observation, including scheduled backoff, and never substitute an internal conversion fork. The probes are read-only and do not start a model turn. Cancel/stop take ownership of waiting handles once, release the original subscription and child, and await pending cleanup before returning.
observedUsage in workflow records and tool/status output preserves the latest reported snapshot for each bound original Planner/Reviewer source. Its version: 1, source: "app_server" and scope: "observed_original_threads" distinguish it from the process-local tokenUsage view. Sources use random accounting IDs; App Server thread/turn IDs are never persisted. Same-thread continuation replaces a cumulative snapshot, while a fresh business review adds a source. summary.total sums only observed source counters; missingSources, partialSources, anomalousSources and optional overflowCounters report gaps. Older records without observations remain unknown.
Missing provider notifications or optional cache-write counts stay absent, rather than becoming zero. Counter regressions preserve the last valid snapshot and mark the source anomalous. If a source uses more than one known model, modelMixed: true replaces a misleading single-model label. These observations include conversions or rewrites performed on the same original thread; separate forks, CLI operations and the parent DSH model remain outside this scope. They are not complete workflow consumption, a price estimate or a hard Token budget.
Observations are atomically merged into the existing SQLite workflow record, independently of business-phase write fences. Usage updates cannot change the verdict, phase, cancellation or submission identity. Completion, cancellation and teardown drain pending observations before releasing original subscriptions; late notifications received during cleanup are awaited. Foreground success output refreshes the recorded observations after cleanup. Successful observations survive unsubscribe, cache eviction and storage reopen; legacy gateways without metering support still run and record missing usage.
Storage failures preserve pending snapshots and expose a fixed usagePersistenceError through status without raw error text. They do not restart a model turn or change its result. A later flush retries the same source identity; teardown keeps a safe in-process diagnostic if the last write failed. A process crash or unavailable storage can lose uncommitted observations, so the saved sum remains only the reported data that was persisted.
Native tool text displays only the observed usage summary, source count and sourcesIncluded: false by default. To inspect per-source history in the displayed response, call codex_workflow_status with { "includeUsageSources": true }. Plans, review findings, evidence and continuation data remain complete. The execution-local canonical value, Manager SDK and CLI show retain the full ledger; a PTC script that explicitly prints that value can still emit the full list. This is a text-payload optimization, not a measured Token or fee reduction.
codex_workflow_submit returns as soon as the submission and evidence are durably stored. A manager-owned App Server review runs in a fresh ephemeral Reviewer; codex exec --ephemeral normalization/alignment produce the internal structured result. The plugin validates and stages that verdict, enqueues the deterministic submit_verdict, and the bridge runtime relays the outcome to the original DSH session. The review never writes the bridge queue itself.
Submission admission captures its input before asynchronous evidence collection. It rechecks the original workflow owner, origin, phase and task scope after collection, after acquiring the workflow lease, and inside the preparation transaction. Missing/non-workspace-readable file evidence is rejected before submission/model/child resources are created; correct the input and use the same workflow. A caller's later input-object mutation cannot bind different input to the collected evidence.
An admitted submission's child startup stays manager-owned until transfer to its background task or cleanup. After each asynchronous creation step, it checks the prepared submission identity, actual lease and current lifecycle again; review-start and heartbeat writes carry that guard inside their transactions. A late child cannot replace another mapped child. Explicit workflow cancellation returns the current cancelled record, and plugin stop awaits pending creation and disposal. If native creation returns after teardown, its original handle is disposed before opening a chat turn. Child disposal failures still release both acquired leases.
The durable preparation commit is the caller-abort boundary: before it, the caller can abort admission; after it, ending only the caller's tool wait keeps that same submission and child startup running. Workflow cancellation, lease loss and plugin stop still take priority. See the child adoption iteration.
Every background callback attempt requires a verifiable original evidence fingerprint and re-reads that original scope before model dispatch, including historical queued/sending/retrying records. Missing baselines, insufficient evidence or a changed workspace fail explicitly without inventing a new review snapshot or spawning a Reviewer. Terminal notices retain their existing one-time identity. See the evidence admission iteration.
No periodic progress messages are injected while a review is running; codex_workflow_status is the on-demand progress view. Busy and rate-limit conditions remain silent background retries — including a transient failure inside the visible review turn, which stays retryable instead of becoming a terminal callback failure. Invalid task ids, missing final agent messages and terminal failures are persisted as an idempotent submission_notice and wake the original DSH session exactly once, including after a plugin restart.
Once a Bridge callback with verifiable original evidence has completed its readable review and structured conversion, an authority-check failure retains that result and the original DSH child in the current manager. Scheduled retries continue validation from this checkpoint and append a short instruction to that child; they send no second readable Reviewer turn. The completed model thread has already released its subscription and pin. Reuse checks the original submission, owner, task/plan, input, model choices and workspace fingerprint, and checks evidence again before staging. Unvalidated checkpoints never publish a pass/result in the review-end projection.
The durable submissionReviewPending flag records only that validation awaits an in-process result. It stores no result, child or ephemeral thread handle. A new manager without that checkpoint reports its loss and refuses to repeat the completed Reviewer. Successful retry-state writes repair the flag after a transient marker-write failure. Cancel/stop clear the runtime result and close the original child once. Progress, heartbeats and lease cleanup cannot write a replaced scope. See the checkpoint iteration and fixed GitHub sources.
Within the current App Server process, a background submission retains its Reviewer and DSH child across scheduled retries. Identical sends share one execution; changes to the submission's source task, workspace, prompt, model or effort are rejected. Retried turns carry only a short continuation; when readable review text already passed the display contract, a conversion failure retries conversion without re-running the workspace review. Scheduled recovery restores running state and the review heartbeat, verifies unchanged workspace evidence, and preserves the staged verdict identity. An observed loss of the original ephemeral process is terminal for that submission; it never becomes a replacement Reviewer. Durable queue and validated-result delivery can recover after a plugin restart. An already started ephemeral Reviewer without its original process-local capability reports unavailability; stored request/evidence cannot silently authorize a replacement for that submission.
A passing verdict tells DSH to report once and end the turn without calling memory, status, todo, shell, or workflow tools. If the terminal relay still leaves that exact agent activity running, a lifecycle guard cancels only the active turn after terminalRelayTimeoutMs while preserving queued inbox work. The guard disarms as soon as that activity reaches idle, so it cannot cancel a later user turn, and plugin teardown aborts and awaits all pending guards.
After Planner work completes, the managed App Server still follows its idle grace period. Reviewer operations are tracked as active turns, and cancel, timeout, lease loss, and stop() interrupt and await them before the App Server is stopped; review completion never reopens or refreshes Desktop.
dsh-codex-workflow respond is a manual/compat fallback only — for operators who want to type a verdict in by hand instead of letting the automatic path collect it, or to re-drive a verdict after the automatic pipeline was interrupted:
$verdict = '{"verdict":"pass","findings":[],"testGaps":[],"summary":"ok"}'
$verdict | dsh-codex-workflow respond --workflow <workflowId> --codex-thread $env:CODEX_THREAD_ID --submission <submissionId> --stdin
dsh-codex-workflow status --request <requestId> --json
--submission <uuid> (optional in respond) pins the verdict to the exact submission the review answered; without it the legacy behavior applies only when the workflow has no active submission. Every respond is validated, idempotent per request id, and replayed safely — it never bypasses the evidence-fingerprint check (a verdict whose workspace changed since review is refused).
The verdict is applied in the original DSH session with the same blocking/non-blocking/no-change/max-cycle policy as the DSH-led flow: blocking findings return DSH to fixing (then re-submit), only non-blocking findings stop at waiting_review_decision for the user, and pass completes the workflow. If the workspace changed between submission and verdict, the verdict is refused and DSH is asked to re-submit for a fresh review — an old verdict can never pass changed code.
The bridge never invents the source task id. dispatch/respond default --codex-thread from CODEX_THREAD_ID and fail with a paste-ready explanation when it is absent. The stored id is the workflow's ORIGIN task: the callback validates it read-only and then creates a private ephemeral Reviewer, so the origin task is never resumed, renamed or written and the review is projected into the owning DSH session.
Both codex_workflow_review and codex_workflow_review_only run the same pipeline: each round creates a fresh ephemeral Reviewer in the NPM Codex CLI App Server, runs the readable review and private structured conversions, then emits the full result to the DSH Session projection. No new Planner/Reviewer thread id is persisted, and a retry or later round never resumes a legacy Desktop task.
The legacy pnpm review-only:accept harness was removed with pnpm lifecycle:accept (see above); use the automated gates instead.
In a DSH conversation:
让 Codex 先规划这个改动,我来执行,完成后再让 Codex 审查。
Tools: codex_workflow_start, codex_workflow_continue, codex_workflow_resume, codex_workflow_review, codex_workflow_review_only, codex_workflow_submit, codex_workflow_decide, codex_workflow_status, codex_workflow_cancel.
The plugin registers one systemPrompt policy section that lets the current DSH model decide whether a user-requested development task should start Codex planning before any implementation change. It does not inspect user messages in a background listener and does not use a keyword classifier.
complex (default): auto-start for multi-file/cross-layer work, architecture/API/data/persistence/concurrency/security/lifecycle/migration/release changes, root-cause-unclear defects needing regression tests, and mature/stable/end-to-end requests. Clear low-risk local edits stay in DSH.always: auto-start for every write-intent development task.off: inject no auto-trigger policy; explicit use of the workflow tools remains available.Questions, explanations, translation, read-only inspection/research and Git-only operations never auto-trigger. A user instruction to work directly, skip planning or not use Codex always wins. Plugin-generated plan/review/fix/submission messages and a session that already owns an active workflow never start another one. When complexity is uncertain, DSH may do only the minimum read-only inspection needed to decide; on a match it briefly announces the decision and calls codex_workflow_start exactly once before modifying the workspace. A session-scoped SQLite lease plus the active-workflow check is the final race-proof guard, so concurrent attempts can create only one workflow and one Planner task.
planning -> waiting_input -> executing -> reviewing -> fixing -> passed
executing/fixing -> codex_workflow_submit (returns immediately) -> queued -> sending -> retrying -> verdict_ready -> received -> applied -> delivered
`-> failed (invalid thread, no verdict, invalid identity/schema)
first sending: read-only origin validation -> create + run an ephemeral App Server Reviewer
same-process retrying -> sending: continue the retained original Reviewer and child
verdict_ready: verdict staged in the record; enqueue pending (crash-recoverable)
received: verdict command queued for application
applied: outcome persisted (pass | fixing | waiting_review_decision | blocked | refused-if-changed)
delivered: outcome relayed to the original DSH session
cancelled: terminal — no queue retry, no late verdict, no message may resurrect it
cancelled is terminal under the bridge too: queued callbacks stop retrying, late verdicts receive an idempotent cancelled receipt and never wake DSH, and duplicate queue files or restarts cannot duplicate turns. While a submission is active, turn-stopping does not ask DSH to submit it again.
coord.sqlite), shared by every DSH process and the CLI. Every invariant runs in a single BEGIN IMMEDIATE transaction; a killed process at ANY point rolls back cleanly and PRAGMA integrity_check stays clean.synchronous=FULL + a busy timeout keep the rollback journal safe for multiple connections. pnpm doctor reports the runtime SQLite version, the actual journal_mode and runs an integrity check; the coordination database is refused on UNC/network paths.status='processing' AND claim_epoch=? AND claim_owner=?. The epoch is NEVER reset (release only clears owner/until), so a stale owner can never re-match a newer claim, and an owner that lost its lease kills its own callback child and stops writing state.nextAttemptAt raises invalid bridge retry deadline and preserves the original claim, attempts, owner, lease and deadline. This prevents unreachable NULL-deadline retry rows; existing queue records are not rewritten. See the deadline and catalog iteration.received -> applied; conflicting request ids are always rejected; the staged identity survives until applied.delivered is written (in a fenced CAS) only after the relay lands. Invalidated passes are reported as void, never as passed; a cancel or new submission that wins before commit never gets marked delivered.bridgeRequestId prevents duplicate workflows and the deterministic relay message id (persisted in the session's agent/inbox/spliced events) prevents duplicate followups.sandboxPolicy: readOnly, no network, approvalPolicy: never); Git, non-Git and nested-repository workspaces use the same path. The codex exec --ephemeral conversions are equally read-only (--sandbox read-only, approval_policy=never) and require no Git root.stop() interrupts and awaits every reviewer operation before the App Server is stopped; provisional normalization/alignment JSON never becomes latestReview.Workflow records, leases, the bridge queue and the live-session registry all live in ONE SQLite database: $DSH_HOME/storages/dsh-codex-workflow/coord.sqlite. The only state on disk outside it is bridge/review-schema.json (the enforced verdict schema) and, briefly, the legacy file-queue source directories that are imported once on first init (receipts, retry semantics and attempts preserved). bridge/sessions.json is gone — live sessions are rows in coord.sqlite (live_sessions) with per-owner leases, so multi-process runtimes merge instead of last-writer-wins and a crashed runtime's sessions expire via TTL. Records never contain login tokens. Old JSON workflow records are imported lazily with origin: "dsh" and keep their behavior.
dsh-codex-workflow is also the audit/ops surface (all commands support --json):
workflows [--cwd] [--dsh-session] [--phase] — list workflow summaries (never payloads).show --workflow <id> — plugin version plus source task id (if supplied externally), stage, submission/callback state, review cycle, last error and evidence summary. New ephemeral Planner/Reviewer ids are process-local and are not persisted; legacy ids remain available only for diagnostics. The internal origin marker (reviewerThreadOrigin) is never surfaced here.queue [--status <status>] — queue/receipt/dead-letter rows with attempts, next retry and last error (never command payloads).retry --request <id> — requeue a dead-letter request or a legacy imported failed row; idempotent, and refuses active or completed rows. A cancelled receipt is stored on a completed done row and is never retried.prune [--older-than <ms>] [--commit] — dry-run by default; --commit removes only terminal receipts and passed/cancelled workflows older than the retention window. Active workflows, undelivered verdicts and failed/blocked diagnostics are never candidates.help — usage.Run pnpm doctor (full: needs Codex CLI + login) or pnpm doctor:offline (CI-safe: skips only the codex/login checks, marks them SKIPPED, still checks SQLite/storage/local paths/build, --json for machines).
A CLI child stopped by a signal can report a null exit code. Existing permission, authentication, schema, display and thread failures still take precedence over that unknown process outcome; they remain terminal and keep their diagnostic. A signal exit without a terminal diagnostic keeps the existing retryable behavior, and explicit caller cancellation keeps its original cause.
Legacy failed-submission migration also uses the shared transient-error classifier before admitting writer-contention recovery. Permission, authentication, thread, display and schema failures keep their original diagnostic and durable failed state even when their text mentions an active writer. Exact legacy busy records still follow the existing bounded recovery path.
Shared Reviewer lifecycle tests pass the native test cancellation signal through their original fixture waits and register idempotent cleanup. A timed-out case remains failed; cleanup must retire only its owned fake App Server and must not strand the full runner. Original scenario deadlines and ownership assertions are preserved.
The multi-manager foreground recovery regression uses one cleanup flight for finally and t.after. It attempts each owned manager, SDK and fixture close even when an earlier stop rejects, then reports the first original error. A separate native-test actor verifies that this failure still exits with code 1 while the original process and storage are closed. This hardens test cleanup; it does not treat unconfirmed production cleanup as success. See the foreground cleanup investigation.
pnpm release:check is a repeatable offline gate: typecheck + full test suite + build + offline doctor (--json, must pass) + a pack audit (temporary tarball is always cleaned) that asserts the package ships only the files whitelist — no tests/fixtures, coord.sqlite, DSH_HOME paths, credentials or temp/review leftovers. CI (.github/workflows/ci.yml) runs the same matrix on Windows with Corepack-pinned pnpm 10 and a frozen lockfile.
Defaults in cordis.patch.yml:
codexCommand: codexautoTriggerMode: complex (off | complex | always)plannerModel / reviewerModel: empty means the current Codex defaultplannerEffort / reviewerEffort: highconversionTransport: app-server (default, warm ephemeral App Server conversion) or cli (explicit cold-start compatibility path)conversionFallback: cli or fail for the existing Bridge/compatibility fallback paths. Foreground review failures keep their selected transport and stage checkpoint; they do not hide permission, Schema or parse errors by switching transports. Select conversionTransport: cli explicitly when required.maxReviewCycles: 3 (1–10)maxNoChangeReviewRounds: 1 (1–10)reviewDiffMaxBytes: 65536 (1 KiB–1 MiB)bridgePollMs: 1000 (200 ms–60 s)bridgeMaxPayloadBytes: 1048576 (64 KiB–16 MiB)callbackTimeoutMs: 600000 (10 s–30 min)callbackMaxAttempts: 3 (1–10 attempts per persistent recovery round)callbackRetryBaseMs: 2000 (200 ms–5 min)transientRetryBaseMs: 3000 (200 ms–5 min; first backoff for a TRANSIENT upstream failure)transientRetryMaxMs: 60000 (200 ms–15 min; final per-attempt backoff ceiling, including jitter and millisecond rounding)transientRetryBudgetMs: 1200000 (0–2 h; total wall clock one operation may spend retrying; 0 disables automatic retry)transientRetryJitterRatio: 0.25 (0–1; ±jitter applied to every backoff, clipped at transientRetryMaxMs)reviewHeartbeatMs: 15000 (250 ms–5 min; period of the DSH-owned Review heartbeat that keeps lastProgressAt/reviewElapsedMs fresh without depending on model output)reviewStaleMs: 60000 (1 s–24 h; a review whose last heartbeat is older than this is reported as stale)reviewerReleaseTimeoutMs: 10000 (250 ms–5 min; deadline for the round's Reviewer-subscription release — the cleanup takes no business signal, so this bound is what keeps a hung thread/unsubscribe from wedging the round and its cancel/teardown)turnTimeoutMs: 600000idleProcessMs: 5000 (starts only after all App Server work is idle)terminalRelayTimeoutMs: 60000 (0 disables; maximum 10 minutes; cancels only a stuck terminal pass relay and preserves inbox work)openCodexDesktopOnReview: compatibility field; the plugin never auto-opens, refreshes or focuses Codex Desktop. Review content is shown in DSH's native subagent chat right sidebar.desktopOpenRetryBaseMs: 2000 (200 ms–60 s; initial retry backoff)desktopOpenRetryMaxMs: 60000 (1–60 s; capped retry backoff)State lives in $DSH_HOME/storages/dsh-codex-workflow/coord.sqlite (queue + leases + workflows + live sessions); bridge/review-schema.json holds the enforced verdict schema. Records never contain login tokens.
MIT
Legacy callback classification also applies to SDK callback children: a signal-only close with a known permission, schema, display or thread diagnostic remains terminal. A signal without a terminal diagnostic keeps its existing retryable path.
Process shutdown uses the same exit predicate as its waiter: a numeric exit code or a received signal records termination. A completed original child receives no further stdin EOF or SIGTERM/SIGKILL escalation. A killed acknowledgement alone is not completion, so a still-live child retains the existing bounded escalation. Direct-child exit remains separate from the captured original process-tree proof used by workflow cleanup.
FAQs
DeepSeek Harness plugin that uses Codex to plan and review while DSH executes.
The npm package dsh-codex-workflow receives a total of 183 weekly downloads. As such, dsh-codex-workflow popularity was classified as not popular.
We found that dsh-codex-workflow demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.