* refactor(state): retire six dead shared-state tables at schema v10
agent_model_catalogs, android_notification_recent_packages,
command_log_entries, diagnostic_stability_bundles, media_blobs, and
model_capability_cache landed with the database-first squash but their
runtime writers never reached main; every stable since v2026.6.10 created
them empty (agent_model_catalogs held only rebuildable catalog cache rows
until #111173 removed its writer). State schema 10 drops all six tables
and seven indexes through both the runtime-open and doctor migration
paths, records the retirements, bumps the native reader ceiling, and
corrects stale database-first doc claims that still named these tables
as canonical stores.
* test: move cross-lane schema-version pins to v10
The v10 retirement missed current-version pins outside src/state: the
native guard vitest wrapper, placement-move and node-worker-launch
same-version assertions, and the audit outbound-progress tripwire. The
pinned pre-C04 audit reader is a v9-era build that now refuses v10
databases by the version contract, so the test projects the file back to
the exact v9 shape with the documented 10-to-9 downgrade fixture before
the reader proof; the shared fixture also seeds the v10 retirement
regression.
* test: keep only the used downgrade fixture export
* fix(cli): keep models --plain stdout clean of startup diagnostics
Extend the models machine-output classifier to recognize --plain and the
parent --status-plain alias so early stream routing sends state database
migration diagnostics to stderr, matching --json behavior. Plain failures
stay human-readable; only --json activates the JSON failure envelope.
The early classifier alone is undone by Commander preaction: preaction
resolves JSON mode as false for --plain and calls
applyResolvedCommandOutputMode(false), which restores stdout routing
before state bootstrap emits the migration diagnostic. Add a separate
retainStderrRouting signal so plain machine-output commands keep stderr
routing through preaction without activating the JSON failure envelope.
Fixes#127494
* fix(cli): preserve plain machine-output ownership during startup
Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>
* test(cli): keep model-output regression within lint limits
Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>
* test(cli): cover models plain stdout routing
* test(cli): preserve canonical model output in plain stdout proof
Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>
* test(cli): align plain output proof with runtime
* test(cli): fix status migration expectation
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Altay <altay@hey.com>
Codex 0.149 emits a session-configure warning whenever UnderDevelopment-stage
features are enabled, and OpenClaw always enables features.code_mode and
features.apply_patch_streaming_events for native code mode threads. The
warning relays into chat as a SYSTEM card telling operators to edit the
managed codex-home config.toml they should never touch.
Send suppress_unstable_features_warning: true in the same thread-config
block that enables those features. When native code mode is off, OpenClaw
does not send the flag, so warnings for user-enabled under-development
features still surface. The general warning relay is untouched.
Verified live against the pinned @openai/codex 0.149.1 app-server binary:
thread/start without the flag emits the exact warning; with it, none.
* fix(test): guard CLI process children against deadlock, not startup
`gateway-backed-exit.process.test.ts` raced a fixed 20s `execFile` deadline
against child startup. Each of its ~37 children cold-loads the whole CLI
command graph through TSX (~2.5s warm, ~16s on a cold checkout, 12-20s on a
loaded host), while every case asserts output, exit code, and state side
effects rather than latency. Under contention the deadline fired first and the
harness rethrew `execFile`'s opaque `Command failed` error with the captured
stdout/stderr discarded, so the failure named neither the deadline nor the
child's last startup step.
`help-exit.process.test.ts` already owned the correct shape: a deadlock guard
sized below the shared Vitest deadline whose failures embed both output tails.
Extract it to `cli-process-child.test-helpers.ts` and converge both suites on
one runner and one `CLI_PROCESS_DEADLOCK_GUARD_MS`, so the policy cannot drift
apart again.
Also fixes the `cron list` promptness case, which timed spawn-to-exit against
10s and therefore measured TSX startup. Its invariant is that a one-shot
command releases its Gateway socket once output is complete, so the clock now
starts at the first parseable JSON payload. That child also gains the
`NODE_DISABLE_COMPILE_CACHE` guard its siblings carry: it owns the NODE_OPTIONS
respawn, and CI's exported `NODE_COMPILE_CACHE` would stack a second detached
respawn on top whose inherited stdio pipes can outlive a killed parent.
The five two-child `it.each` cases split into one child each so a single guard
budget covers the file, and the hand-tuned per-case Vitest deadlines are gone.
* fix(test): release child pipes when the deadlock guard fires
The guard's SIGKILL reaches the launcher only. A respawning entrypoint hands its
stdio to a detached grandchild in its own process group, so that grandchild
survives the kill and keeps the runner's pipe ends open after the guard has
already rejected — the "still running with no output" stall this suite exists to
remove, reintroduced by its own guard. The cron case deliberately enables that
respawn, so the path is reachable, not hypothetical.
Release our ends of the pipes alongside the kill. Waiting for a process tree we
cannot reach would defeat a deadlock guard, so the orphan is left to die on EPIPE
or be reaped with the runner.
The regression drives the same topology: a launcher that hands stdio to a
detached grandchild which writes only after the guard fires. Pre-fix the runner
still receives that write ("expected [ 'launcher', 'launcherafter-guard' ] to
have a length of 1 but got 2"); post-fix it never arrives.
Reported by ClawSweeper on #129489.
* perf(ui): speed up session switching and transcript rendering
* test(skills): assert canonical learn authoring standards
* test(ci): include startup retry coverage in its owning shard
* test(agents): reuse scoped metadata in alias regression
* fix(worker): honor full access on paired node sessions
* test(worker): preserve narrowed operator in live proof
* test(vitest): assign Codex startup retry to its owner shard
* fix(i18n): keep surrogate pairs intact when bounding process output tail
appendBoundedProcessOutput used nextText.slice(-maxChars) to keep the
newest maxChars of captured process output. When the boundary landed
inside a UTF-16 surrogate pair (e.g. emoji in stderr), the retained
tail began with a dangling low surrogate, corrupting downstream JSON
serialization and fatal TextDecoder paths.
Switch to sliceUtf16Safe(nextText, -maxChars) from normalization-core,
which adjusts the boundary off the surrogate pair. The helper was
already imported elsewhere in the dependency graph.
* fix(i18n): count actually-dropped units when bounding surrogate-safe tail
Address ClawSweeper P2 finding on PR #120114: sliceUtf16Safe may
return fewer than maxChars code units when it advances past a low
surrogate at the boundary, so truncatedChars must derive from the
actual retained tail length rather than maxChars. For the emoji
case ("ab😀cdef", maxChars=5) the safe slice retains "cdef" (4
units), so 4 units are dropped, not 3.
Compute truncatedChars from nextText.length - text.length and
update the regression test expectation to 4.