* fix(pairing): honor gateway.publicOrigin for device join codes
A loopback Gateway behind public HTTPS ingress sets gateway.publicOrigin, and
cloud node enrollment already used it, but device join codes, the device-pair
plugin, and Doctor's node-hosting check each resolved the pairing endpoint on
their own and ignored it, failing with advice that omitted publicOrigin.
Make src/pairing/setup-code.ts the single owner (device-pair publicUrl
override, then gateway.publicOrigin, then existing discovery), expose it to
the device-pair plugin through plugin-sdk/device-bootstrap, remove the
plugin's duplicate resolver and enrollment's private fallback, and share one
loopback error that names gateway.publicOrigin.
* fix(pairing): preserve explicit remote endpoint selection
Honor preferRemoteUrl before gateway.publicOrigin so qr --remote keeps
its selected endpoint aligned with remote credentials. Retain publicOrigin
as this Gateway's ingress ahead of automatic Tailscale, remote, and bind
discovery, with the pairing-specific override taking precedence.
Cover configurations with both URLs at the resolver and QR CLI boundaries,
and correct the CLI, cloud-worker, and SDK precedence documentation.
Validation: 221 focused tests passed; scoped core, extension, script, and
test typechecks passed; independent review found no actionable P0-P2 issues.
* fix(pairing): preserve device routes with explicit cloud ingress preference
Keep existing device join-code, QR, and /pair discovery order. Use
publicOrigin only at the loopback fallback, while cloud enrollment asks the
same resolver to prefer public ingress at both preparation and issuance.
Use the canonical lazy runtime binder for the SDK export so the collision
guard recognizes one implementation without loading setup code at startup.
Document the two call-site intents and cover Tailscale, LAN, loopback, and
cloud enrollment behavior.
Validation: export-name collision and SDK surface guards passed; 222
focused tests and scoped typechecks passed; independent review was clean.
* chore(device-pair): drop the max-lines suppression the smaller plugin no longer needs
* fix(device-pair): preserve origin-only setup URLs
Retain the plugin command URL mapping through the shared pairing resolver while preserving context paths for join codes and cloud enrollment. Clarify public-origin fallback and remote QR prerequisites.
* fix: keep gateway startup retryable after failed reads
* fix: retain terminal agent ownership refusals
* fix: preserve confirmed startup refusal causes
* fix(gateway): preserve startup refusal evidence across reads
Keep unavailable config, backup and SQLite reads retryable while preserving
confirmed config, schema, ownership and maintenance refusals as exit 78.
Carry failure causes through existing admission owners without changing
public refusal data, persistent schemas or migration policy.
Resolve the main integration and retain its identity-store ownership.
Include exact upstream test-call corrections needed by the current helper API.
* test(gateway): narrow startup failure fixture variants
* test(state): expect typed schema refusal on committed reads
* fix(state): classify expired schema read scopes for actor retirement
Reuse the established read-admission invalidation type when a managed schema scope ends. Preserve original scope rejection and the worker owner’s relocation and active-callback fences while allowing idle old actors to retire for valid replacement callers. Integrate current main and prove ordinary and same-path managed replacements with native workers.
* test(media): use valid PNG in Windows file URL fixture
* chore: reconcile startup read repair with main
Take the upstream schema-scope production correction unchanged. Preserve complementary fresh managed-scope and retained prepared-reader coverage with the upstream typed refusal assertion, and retain the main iterative include scan beside the reviewed config read helpers.
* fix(subagents): preserve resident source admission failures
Use canonical cache absence semantics in both snapshot identity getters. Remove catches that translated expired current read scopes into missing cache facts; preserve full-row publication handling and same-file source renewal. The unchanged scope regression fails before this correction and the registry, lifecycle and projection suites pass afterward.
* Merge main to retain its Windows media fixture repair
* Merge main while preserving its worker-lifecycle reverts
* fix(state): retain complete typed startup refusal reports
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Remove Tasks and TaskFlow runtime, APIs, CLI, SDK surfaces and panels after the Cron, session, native execution and media completion ownership cutovers. Preserve stored rows and import provable legacy native assignments through Doctor; ambiguous ownership stays untouched with a warning.
Follows #158221, #158217, #158225, #158222, #158702 and #158776. Related: #156532. Task-specific public APIs retire immediately; retained responsibilities use their existing owners.
Maintainer-authorized administrative landing after full CI run 36312986498 attempt 2 passed on 274595e2, with subsequent actual conflicts reviewed and focused checks passing. Current PR CI preflight hits the 64 KiB changed-path metadata limit before tests (run 36335042695); its duplicate security-review status mirrors that planning failure. Review and scoped proof are recorded in the PR. Published 9.4 native import is proven; remaining native completion and 9.4 rollback witnesses are explicitly unproven.
Distinguish invalid timezones from invalid datetimes at the zoned parser owner, and preserve the scheduler caller's existing failure behavior. Share timezone validation across cron add/edit schedules so valid timestamps are no longer blamed for invalid --tz input.
Add registered add/edit regressions and retain invalid-calendar and DST-gap controls. Blacksmith proof captured seven failing CLI cases before the fix, 79 passing CLI tests and 75 passing parser tests after it, 624 passing focused tests, and a passing check-changed gate. No local test, build, install, or formatting commands were run.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Use the shared CLI failure envelope for config unset and patch usage errors when JSON output was requested. Preserve config set's parse-only JSON alias and existing dry-run results.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(sessions): fail tail for missing explicit keys
Report an actionable error and exit nonzero when sessions tail cannot find
an explicitly selected session. Keep an empty implicit selection a
successful no-op, including follow mode.
Add command-owner regressions for explicit missing keys with and without
follow, plus preservation of the implicit empty-selection behavior.
Document the distinction in the CLI guide.
Blacksmith Testbox proof: the unchanged owner fails exactly the two new
missing-key assertions (26 pass); the fixed owner passes all 28 tests and
the registered CLI sibling passes all 53 tests. Independent review is clean.
Changed checks reach the unrelated provider-lifecycle.ts TS2322 already
fixed upstream by a241b3bf6e.
* fix(cli): reject blank session keys in sessions tail
* test(sessions): avoid shadowed session key in tail regression
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Reject unsupported timeouts before creating system-event jobs instead of silently dropping the option. Share payload timeout support validation with edit while retaining script guidance and system-owned job protection.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(update): keep waiting while candidate startup progresses
Renew the existing size-based canary allowance from completed CLI and Gateway milestones. Preserve recorded warnings, reject stalled candidates, and observe process exit without waiting for inherited pipes.
Refs #157332. The published 2026.9.4 parent deadline is unchanged; Linux proof found private schema migrations and did not reproduce the reported old-reader mismatch.
* fix(update): bound candidate startup validation
* fix(daemon): inspect registered Gateway services accurately
Read strict Windows service commands from registered actions and preserve
supported launcher encodings without treating dynamic CMD expansion as a
verified command. Retain exact task identity and bound systemd selection.
Use one platform inventory collector for the existing full and diagnostic
projections. Report incomplete inspection through Doctor while preserving
status JSON and the existing managed-service filters. Keep load-state
inspection behind a leaf capability instead of reverse facade imports.
Refs #151608, #151463.
* fix(daemon): classify service commands by position
Share runtime and root-option parsing so Node display names and profile values
cannot classify a Node service as a Gateway. Preserve literal shell, env and
generated launchd wrappers, and honor the executable selected by launchd.
Read systemd's single-quoted arguments through the existing parser and preserve
literal apostrophes when rendering. Keep strict Windows command rejection
separate from lenient diagnostics, and align the native-boundary fixtures with
that contract without weakening ownership or source-preservation assertions.
* fix(daemon): inspect direct registered task actions
Read literal executable actions from their registered Scheduled Task and
revalidate the action before returning command facts. Strict runtime inspection
uses the same registered command instead of a default launcher.
Keep executable paths out of managed launcher provenance. Direct actions remain
outside automatic update service management when no restorable CMD/VBS launcher
exists, preserving the prior stop and definition ownership boundary.
* test(windows): prove installed service upgrade paths
Extend the existing native Scheduled Task proof with verified immutable package
handoff and fixed fresh, published 2026.9.3, and published 2026.9.4 CLI cells.
Require live version/build identity, selected PID replacement, peer continuity,
strict registered-action inspection, and settled cleanup before evidence
publication and disposable installation retirement.
Keep source-only and repair modes, permissions, deadlines, and native lifecycle
owners intact. Direct executable fixtures remain disabled and preserve the
updater's unsupported-mutation boundary. Native execution remains pending.
* test(doctor): retain managed Windows launcher provenance
Keep the generated CMD path on the existing managed-service fixture before
and after reinstall so Doctor admission sees the installation it models.
Preserve all stop, install, restart, rollback, and direct-action refusal
expectations without changing production behavior.
* test(windows): exercise native autostart ownership boundaries
Extend the existing published-updater cell with its stopped, task-owned
Scheduled Task. Capture real admission, exercise native enable/disable,
and preserve the definition and files after foreign-owner refusal.
Test retained admission separately from restoration so legitimate same-root
refresh remains supported. Restore the original XML through the existing
fixture lifetime; do not broaden product control or workflow permissions.
Modeled owner tests, selected source checks, and independent review pass.
Actual Windows execution remains required before a native proof claim.
* test(windows): retain sanitized installed command failures
Keep unexpected command stdout and stderr in bounded failure diagnostics so
JSON-mode CLI errors survive native proof failures. Reuse the existing
terminal and support redaction owners before clipping; withhold incomplete
captures while preserving the existing truncation failure.
Move the existing command runner into its own test-support module and update
both consumers without changing timeout, exit, signal, or cleanup semantics.
Real-child regressions reproduce both privacy defects and pass with the
correction. The original installed Gateway failure remains undiagnosed.
* fix(daemon): preserve Windows probe budgets and Doctor cleanup eligibility
Use the existing cold PowerShell startup budget for Task Scheduler queries
without an explicit deadline. Keep periodic activation checks bounded and
preserve unknown results rather than treating timeouts as task absence.
The native probe test now exercises the production default.
Share Doctor's existing legacy cleanup classification with its registered
preview. Keep unsupported platforms, scopes and unrecognized Linux unit
names as findings without advertising removal or invoking unrelated cleanup.
Extract the classifier and complete cleanup test group without dropping
assertions or changing native mutation ownership.
Retain the failed installed Windows runs and their source identities;
new package and native upgrade qualification remain separate requirements.
* chore(daemon): retire stale Doctor size baseline
The split Doctor service module no longer needs a max-lines suppression.
Remove only its stale baseline entry so the shrink-only ratchet matches
actual source. Product, dependency, workflow and fixture bytes are unchanged.
* test(windows): retain sanitized service proof observations
Record bounded install and status facts before semantic assertions so a
successful CLI exit cannot hide the native inspection reason. Exclude
free-form stderr and private response fields, and preserve existing
execution, deadline, and cleanup assertions.
* fix(windows): preserve native status inspection defaults
Keep the CLI RPC default distinct from an explicit timeout so Windows
service inspection can use its existing cold-start budget. Forward
explicit load-query deadlines through the Task Scheduler owner while
preserving lifecycle defaults and timeout diagnostics.
* fix(models): retain discovered models after refresh failures
Record successful legacy catalog results at the producer boundary so unavailable refreshes retain the accepted inventory. Preserve explicit outcomes, advisory SDK fallback behavior, and first-discovery starter policy.
* fix(models): preserve skipped catalog outcome semantics
Mark bundled static, configured, and advisory catalog projections with
explicit empty outcomes so legacy success inference cannot promote them
to observed account inventory. Preserve live outcomes and helper types.
Keep exact auth provenance histories and move existing fixture/policy
code into focused owners where required by the line-cap ratchet.
Validation: 447 producer and sibling cases, 56 shared self-hosted cases,
95 auth/policy cases, causal missing-outcome failures, maintained checks,
and independent review.
* test(plugin-sdk): keep discovery loader types acyclic
Move the shared loader type into a leaf consumed by both discovery
contract helpers. Preserve its public provider-test-contracts export
without a child-to-parent type import cycle.
Validation: maintained Madge check reports zero cycles; core, all core
test graphs, extension test types, lint, formatting and independent
review pass. Runtime behavior and previous catalog proof are unchanged.
* fix(plugin-sdk): mark generated static catalogs explicitly
Keep the generated non-live, non-strict catalog adapter from claiming
successful acquisition for manifest or configured rows. Preserve null,
errors, strict and custom callbacks, static catalogs, and public types.
Validation: three existing controls fail before the correction; all49
owner and sibling cases pass afterward, with types, lint, line caps and
fresh independent review clean.
* test(windows): handle omitted scheduled task settings
* test(windows): preserve native context and failure evidence
* test(daemon): wait for installed gateway readiness before inspection
* test(daemon): stop installed fixtures through their profiled CLI
* fix(daemon): inspect extra Windows services before removal
Keep verified Node and legacy diagnostics visible while offering read-only
Scheduled Task inspection in Doctor and deep status. Preserve the existing
service cleanup owners and diagnostic JSON shape.
Carry the canonical SQLite fixture host-context and dependency-selection
repairs from #158209 and #158409 for the inherited CI collection failure.
* fix: restore asynchronous harness task completion
Complete the shared task-content projection cutover for asynchronous finalization and delivery. Preserve the captured Incognito policy and exact task-assignment fences.
The unchanged worker suite reproduced six ReferenceError failures before the fix and passes all seven cases afterward; the sibling SDK runtime suite passes all 30 cases. Independent review is clean through P2.
* test(windows): budget installed service phases and retain progress
* test(windows): retain failed installed update progress
Read failed published-updater progress through the existing asynchronous SQLite owner before native cleanup. Retain only safe phase, status and step timings; preserve all command, body and teardown deadlines.
Validation: five installed-fixture tests, services types, targeted changed checks and independent review through P2 passed. Shared diagnostic implementation qualified in the Windows fixture owner.
* fix(daemon): bound aggregate Windows service inventory
Carry one monotonic deadline through Scheduler discovery, launcher reads, registration revalidation, and missing-launcher metadata. Preserve completed findings and report incomplete inventory when the shared budget is exhausted.
* fix(daemon): classify registered helpers without profile admission
Let read-only registered inventory classify faithfully revalidated commands
without requiring an OpenClaw profile. Keep selected-service profile
admission strict and preserve launcher, Task, script and deadline checks.
Native disabled-discovery exposed a false warning for a static node --version
helper. The actual collector regression fails before this fix and passes
with all 206 owner and sibling cases afterward.
* fix(daemon): recognize released waiting task launchers
Recognize the exact waiting VBS wrapper shipped by 2026.9.3 during
owned service reconciliation. Keep custom launcher behavior unknown and
preserve all command, root, Task and authority checks.
The real audit entry point rejected this released form as TaskLauncher
unknown-edit before repair. The causal regression and edited-launcher
control pass with 55 audit and 140 backup/rewrite sibling cases.
* test(daemon): cover retained Task ownership refusal
* test(windows): preserve XML bytes around enabled export lines
* refactor(update): reuse npm retry arguments and trim dead package checks
* refactor(update): derive plugin tree types from the plan schema
* refactor(update): reuse mutation authority for package retirement
* fix(update): classify candidate cleanliness before reporting completion
Reuse the clean-check owner and inline single-use Git update helpers. Preserve selected-commit validation and failed-checkout rollback.
* refactor(update): drop cyclic plugin-tree type consolidation
Restore both original type shapes for B1. Sharing the plan schema without a static import cycle requires a wider owner change, outside this cleanup scope.
* test(update): align npm retry argument expectation
* refactor(plugins): deslop workboard, voice-call, reef and line second pass
Consolidate private projections and lifecycle plumbing, remove unreachable helpers, and derive Swabble CLI dispatch from its command tree. Preserve plugin, media, webhook and persisted-state contracts.
Fix Workboard Doctor migration aborting on a null legacy attachment; retain malformed source records while migrating valid attachments. Regression fails on the original null dereference and passes on the repaired owner.
* refactor(workboard): preserve generated browser asset identity
* test(signal): reuse container transport fixtures
* fix(reef): keep update rehearsals from migrating live files
Declare Reef migration resources and defer whole plugin state migrations when
their declared files lie outside the copied rehearsal state. Preserve pending
and required migration facts, configuration repair, and normal live Doctor.
Use one resource owner for inventory and execution, rechecking selected paths
before writes, and retire the superseded private collection wrappers.
Registered-runner regressions reproduce live-file mutation on the original
code and cover direct, automatic, and post-session repair paths.
* refactor(doctor): simplify rehearsal resource projection
* ci: refresh merge validation after upstream suppression repair
Revalidate the PR merge context after main e6fae68631 repaired the plugin suppression inventory. The reviewed source tree and runtime proof inputs are unchanged.
Record process identity for schema/media migration leases and reclaim only demonstrably dead process owners. Preserve expiry for work that can outlive its parent, existing snapshots, and transactional migration recovery.
Refs #158114. Linux SIGKILL recovery and three focused passes passed; 91 dependent files and changed checks passed; P1 autoreview clean.
Verify historical archive receipts with device tolerance for normal restores and interrupted publications, then bind strict publication checks to the captured live identity. Preserve recorded receipts and reject changed originals.
Blacksmith proof: both unchanged-artifact regressions fail before the fix and pass afterward; 63 focused tests pass with 2 platform-specific skips. check-changed and isolated review pass. Existing receipt formats and updater behavior remain unchanged.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Report nonzero completion when config, credential, session-directory, state, or workspace cleanup fails. Preserve best-effort cleanup and show the onboarding hint only after complete success.
Live QA reproduced a locked config removal reporting EPERM with exit 0. Blacksmith regression proof: 7 expected failures before the fix, 45 reset and sibling tests passing after it, changed-file checks passing, and independent Codex review clean.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Match channels/<channel> logger roots and their descendants so runtime startup and provider errors appear in filtered text and JSON logs. Preserve exact channel boundaries and the existing documented namespaces.
Regression proof: both new namespace cases fail before the fix. All 72 scoped command and CLI tests and the changed-file checks pass on Blacksmith Testbox. Independent review found no actionable P0-P2 findings.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Read channel installation facts from the prepared metadata snapshot with disabled owners included. Keep setup activation decisions unchanged and preserve absent catalog channels as installable.
Add a command-boundary regression for text and JSON. Both cases failed before the fix; 22 channel-list tests and the changed-file gate passed on Blacksmith Testbox. Clarify that disabling a plugin does not uninstall it.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Return authoritative Gateway request errors through the normal CLI failure path instead of reporting the Gateway unreachable with exit 0. Keep connection failures on the existing config-only fallback.\n\nReproduced unknown channel status on built main 71ca294e23 through an isolated lane CLI. Blacksmith Testbox regression: both new cases fail before; 31 tests pass after. Remote check-changed passes; independent review has no actionable findings.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(sessions): preserve remote cleanup destination on connection failure
Restrict automatic offline cleanup to the configured local Gateway. Pin the resolved RPC destination while preserving its authentication selection, so remote configurations and environment URL overrides cannot silently clean local stores when their connection fails.
Regression: two refused-loopback-tunnel cases fail on the original code and pass with the fix. Blacksmith validation passed 191 focused tests and check-changed. The changed command suite took 18.684s wall with one worker. Independent review found no actionable issues through P2.
* fix(agents): preserve remote deletion targets on pre-connect failure
Share the pre-dispatch fallback decision with sessions cleanup under the Gateway call owner. Pin agent deletion to the classified config and URL; remote transport and credential failures now leave local agent state intact and explain how to restore the connection or run on the Gateway host. Preserve local credential fallback with skipped cron cleanup and keep ambiguous transport errors unchanged.
Four remote-target regression cases failed before the fix against the agents-delete implementation identical to origin/main. Blacksmith Testbox passed all 227 tests across the agents deletion, sessions cleanup, and Gateway call suites (41.36s total), plus check-changed. The new regression cases took 0.5-0.7s each. Isolated review was clean through P2 after replacing an expired reviewer login with the existing Testbox API credential. Local hooks were skipped to honor the remote-only check requirement.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Reject unknown models.list provider filters with INVALID_REQUEST instead of letting a generic exception become UNAVAILABLE. Name the rejected provider and point callers to openclaw models list --all. Preserve valid provider filtering and genuine availability errors.
Move existing provider-filter coverage to the registered handler and verify CLI message propagation and exit code 1. Update CLI and RPC documentation.
Validation on Blacksmith Testbox tbx_01m3ftahjxpg1pz589w82vtz6f: baseline regression failed with the generic provider error (1 failed, 14 passed); repaired handler, model/session, and CLI suites passed (109 tests). Full check-changed and independent P0-P3 review passed. Local hooks disabled because all checks were required to run remotely.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Use the shared root argument parser to distinguish valid bare launches from unknown or incomplete options. Leave invalid input to Commander so a typo reports an option error instead of opening onboarding or the TUI. Preserve the terminator and valid root-selector behavior; cover fresh and configured entrypoints with real Commander parsing.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(proxy): keep capture persistence off the main thread
Move bundled capture writes, payload compression, and inspection through the canonical SQLite workers. Preserve shipped synchronous SDK compatibility and exact database, maintenance, and accepted-callback ownership.
Drain accepted capture before shutdown, release partial proxy startup, and charge queued payload preparation through the existing broker. Preserve schemas, stored bytes, and retention. Related: #148336.
* chore(proxy): reconcile capture activation with current main
* chore(proxy): preserve current SDK surface counts
* refactor(proxy): isolate async capture store contract
* fix(proxy): include worker dependencies in PR wrapper
* chore(proxy): compose capture migration with current main
* test(proxy): finish database worker lane routing
* test(proxy): retain Node coverage after worker lane migration
* fix(proxy): drain active captures before signal exit
* fix(proxy): preserve legacy capture cleanup in mixed sessions
* fix(proxy): preserve capture metadata and composed worker routes
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Use the inherited update run ID when candidate inspection has no explicit
run context, then retain the existing live handoff lease verification.
Preserve refusal for missing run IDs, stale leases, and genuine Gateway
children. Correct the documented service boundary for systemd scopes.
Real systemd update.run now replaces the package and restarts the Gateway
successfully; focused regression coverage passes three times.
* fix(doctor): migrate every agent database before repairs open it
Complete configured session-store targets before media/schema repair.
Recognize intact pre-journal v1 state without releasing lost-history holds,
and fail explicit Doctor repair when those holds remain unresolved.
Refs #158359.
* fix(tooling): include journal admission in wrapper inventory
* fix(doctor): admit pre-journal v1 stores with writer metadata
Treat app_version as optional writer metadata rather than evidence that a
schema-v1 store had a deletion journal. Keep the schema, ownership, newer-table,
journal-receipt, and recovery-hold guards intact.
Register the agent database ordering regression with the runtime-build
prerequisite owner so it runs in a clean checkout. The two CI runtime-inventory
expectation updates add exactly this newly registered test input; they preserve
exact equality, runtime preparation, ownership, and worker-bound checks.
Refs #158359.
* test(state): require initialized journal after v1 admission
The accepted production behavior intentionally initializes the canonical
agent_deletion_journal when admitting a valid pre-journal v1 store. Update the
v2026.7.1-2 fixture contract to require the complete canonical schema and assert
that the deletion-history owner reports known-empty history.
This expectation changes because journal initialization changed intentionally
in production, not to silence the check. Preserve the full schema comparison,
frozen fixture hashes, retained-data checks, and modern lost-history assertions.
Refs #158359.
Verify legacy event containment in the shared comparator, import only missing suffixes, and preserve conflicting originals with named event diagnostics. Record superseded transcript counts in existing archive receipts and keep clean recovery out of support reports. Preserve malformed-tail warning and protected-archive behavior.
## What Problem This Solves
Fixes: owners who ask their agent in chat to change a key, a config value, or one of their own skills get refused, sent to a dashboard, or told to file a Workshop proposal. Examples: "you can't post API keys here", switching the embeddings provider routed to the web-search wizard, only proposals for handwritten skills.
## User Impact
An owner can hand the agent an API key or token in chat, ask it to change config such as the embeddings provider, and have it edit skills they own. Session permission modes are unchanged: Full Access applies, restricted sessions ask.
- **Keys from chat.** Masked setup flows still keep keys out of model context and remain the default. If the user already pasted a key or token, the agent stores it in the shared secret store and points the config key at it with a `store` SecretRef instead of refusing. It never echoes the value back. The pasted message already reached the model provider and transcript; redaction covers later logs and output only, and the docs say so.
- **Existing store entries are never touched.** Each save inserts a new entry named after the config key plus a random suffix (`GATEWAY_REMOTE_TOKEN_9B139B5E231299BC`). Nothing is overwritten, revived, or deleted, and a new name can never match anything already pointing into the store, including a stale reference to a removed and purged entry. Replacing a key leaves its previous entry for `openclaw secrets store rm`. Rotating a key keeps its configured store provider alias, and the audit records the alias actually used.
- **Embeddings.** The agent now treats the memory embeddings provider, model, and key as `memory.search.*` config, not the web-search setup wizard.
- **Skills.** When the user asks, the agent edits skills they own directly: repository skill source, workspace `skills/`, project `.agents/skills/`, and configured extra skill directories. Bundled, ClawHub-installed, and plugin-provided skills are replaced by their owners' updates. For those, the agent says so and offers to capture the change as a Workshop skill.
- **Tone.** The "never request / paste credentials in chat" lines are gone from the `openclaw` tools and system-agent prompts. "Never echo secret values" stays, and so do factual pointers for flows that genuinely need a UI: channel sign-in, provider OAuth/accounts, and model onboarding.
No config option, schema, or protocol change. The Full Access permission-policy floor from the first revision moved to #158142 for its own security review.
## Why This Change Was Made
After #149870, approved config writes may target any path. What still blocked owners was model-facing text telling the agent to refuse credentials, plus the missing ability to store a chat-provided value anywhere but plaintext config.
`config_set_ref` gains an optional `secret` argument (read without trimming; only emptiness is checked). With it, the system agent:
1. registers the value for redaction when the proposal is built;
2. keeps the key's existing store provider alias when it has one;
3. sends one `secrets.writeForConfigRef` command to the SQLite state worker with the requester's live-authority guard. The host re-checks that guard at the worker's transaction and commit admission (`createSqliteWorkerWriteAdmission`), so a run stopped while the command is queued writes nothing. The transaction inserts a new row under a freshly minted `NAME_<16 random hex>`;
4. writes the ref through the existing config writer, which re-checks authority. If that write fails (before or after the writer commits), OpenClaw rereads the config and the error says the key was saved as `<NAME>` and whether the config key points at it. There is no automatic delete: another consumer may have linked the fresh entry, or the writer may have committed before failing;
5. the normal config reload picks up the new ref, since its id always changes.
Nothing new runs SQLite on the Gateway main thread.
<details>
<summary>Out of scope / follow-ups</summary>
- Found while proving this: in Full Access, after a delegated change applies, the next agent turn in the same chat fails with `SQLite database already belongs to another worker backend`. It reproduces on unmodified `origin/main` (`71bb516`) with a `logging.level` change followed by one more message. This PR does not fix it.
- Built-in provider sign-in and model onboarding stay handoffs; they own live verification of the active inference route.
- Other secret-store set/delete paths remain synchronous migration debt, as `worker-access.md` already records.
</details>
## Evidence
Real Telegram Test Server (Convex-leased userbot, fresh Gateway, QA mock provider, Full Access, tester is owner), first revision:
| | Screenshot |
|---|---|
| Token given in chat, applied with no approval prompt and no refusal (synthetic QA token) |  |
### Final effects at this head
qa-channel scenario `system-agent-owner-trust` passes through a real Gateway and state worker. The Gateway is seeded with an unrelated `GATEWAY_REMOTE_TOKEN` entry, then:
1. A command-allowed **non-owner** (`bob`) sends the key. The `openclaw` tool is owner-only.
2. The **owner** (`alice`, Full Access) sends it.
Captured step details (redacted by the Gateway; the store ref id prints as `__OPENCLAW_REDACTED__`):
```json
{
"nonOwnerEntryNames": ["GATEWAY_REMOTE_TOKEN"],
"storedRef": { "source": "store", "provider": "default", "id": "__OPENCLAW_REDACTED__" },
"storeEntryNames": ["GATEWAY_REMOTE_TOKEN", "GATEWAY_REMOTE_TOKEN_9B139B5E231299BC"]
}
```
- After the non-owner turn: only the seeded entry exists and `gateway.remote.token` is unset.
- After the owner turn: `gateway.remote.token` is a `store` SecretRef, the token is in its own minted entry (`GATEWAY_REMOTE_TOKEN_9B139B5E231299BC`), and the seeded entry's `updatedAt`/`updatedBy` are unchanged. No approval prompt was posted, and the token is absent from chat, config, and the store listing.
**Revoked request**, through the production worker (Node main thread, real broker, `writeSecretStoreEntryForConfigRef`). The requester's guard passes the caller's check, then reports the run stopped:
```text
seeded: [ 'GATEWAY_REMOTE_TOKEN (cli)' ]
revoked request rejected: requesting run is no longer active
after revoked request: [ 'GATEWAY_REMOTE_TOKEN (cli)' ]
owner request saved as: GATEWAY_REMOTE_TOKEN_F1657B971F691824
after owner request: [ 'GATEWAY_REMOTE_TOKEN (cli)', 'GATEWAY_REMOTE_TOKEN_F1657B971F691824 (openclaw)' ]
seeded value intact: true
```
Tests (each fails without the behavior it covers):
- production worker path (`secret-store-config-ref.worker.test.ts`, forked database-worker lane with the real broker): a chat secret gets its own minted entry beside a live `GATEWAY_REMOTE_TOKEN` without touching it; a requester revoked after the caller's check writes nothing;
- store kernel: a refusal at commit admission rolls the transaction back; each save mints a new `NAME_<hex>` and leaves the key's previous entry unchanged; a stale name whose entry was removed and purged still resolves to nothing after a chat save;
- operations: stored and referenced with no value in output or audit; authority gone before the store write writes nothing; a failed config write names the saved entry and leaves it in place; rotating a key keeps its configured store provider alias, and the audit records it;
- tool: proposes a store write without repeating the key, preserving leading and trailing whitespace.
Measured single-worker wall time per new or materially changed test file at this head (`node scripts/run-vitest.mjs run <file>`, local M-series; vitest Duration includes import and setup):
| File | Tests | Wall | Vitest duration |
|---|---:|---:|---:|
| `src/secrets/store/secret-store-config-ref.worker.test.ts` (new, database-worker lane) | 2 | 14 s | 2.05 s |
| `src/secrets/store/secret-store.test.ts` | 32 | 16 s | 13.37 s |
| `src/system-agent/operations.test.ts` | 43 | 18 s | 15.30 s |
| `src/agents/tools/system-agent-tool.test.ts` | 36 | 15 s | 12.89 s |
QA scenarios: `system-agent-owner-trust` (mock-openai) runs in about 27 s after build; `skill-owner-direct-edit-live` is live-frontier only and took about 3 min with `claude-cli/claude-sonnet-4-6`.
Wording pins for the removed lecture text were deleted. The focused store, worker, exclusivity, operations, tool, approval, and delegate suites pass. `node scripts/check-changed.mjs` passes every gate except core lint, which fails only on three files this PR does not touch (`server-chat-metadata-lifecycle.integration.test.ts`, `session-companion-ask.ts`, `app-sidebar-session-list-render.ts` over `max-lines` on the base); oxlint on the changed files is clean.
Security decision: a Full Access owner's pasted key goes to the Gateway-wide team store without a separate approval. Maintainer (@obviyus) accepted this in the PR conversation.
**Rotation with a second consumer**, through the production worker (Node main thread, real broker). A second consumer references the key's first entry before the next save lands; value fingerprints only:
```text
owner saves key #1 -> MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 (sha256:4a5c5a4aa8de)
second consumer now references MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 (e.g. linked while the next save is queued)
owner saves key #2 -> MODELS_PROVIDERS_OPENAI_API_KEY_4066F18ABAAE6903 (sha256:28bc4e3fe10d)
second consumer's entry MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 after rotation: sha256:4a5c5a4aa8de
unchanged: true
```
**Config write fails after the save, with a second consumer on the fresh entry**, through the production worker and the system-agent apply path (fingerprints only):
```text
owner result: Saved the secret as GATEWAY_REMOTE_TOKEN_6B68C031E571F81C, but could not point gateway.remote.token at it: config write failed after commit (rollbackStatus: not-restored). Retry, or remove the entry with `openclaw secrets store rm GATEWAY_REMOTE_TOKEN_6B68C031E571F81C`.
config gateway.remote.token: null
second consumer's entry GATEWAY_REMOTE_TOKEN_6B68C031E571F81C: sha256:09ae5b4fd36b
second consumer keeps the credential: true
```
**Stale reference to a removed and purged entry**, production worker for purge and save:
```text
purged rows: 1
stale ref GATEWAY_REMOTE_TOKEN after purge: SECRET_STORE_NOT_FOUND
chat save -> GATEWAY_REMOTE_TOKEN_8F698B5396990ADD resolves (value hidden)
stale ref GATEWAY_REMOTE_TOKEN after chat save: SECRET_STORE_NOT_FOUND
```
**Owned-skill edit with a live model.** New scenario `skill-owner-direct-edit-live` (live-frontier; run with `claude-cli/claude-sonnet-4-6`, subscription auth) passes at this head. It seeds workspace skill `qa-owner-greeting` replying `OWNER-GREETING-V1`, and the owner asks in plain words: "Please change my qa-owner-greeting skill so it replies OWNER-GREETING-V2 instead of OWNER-GREETING-V1." Captured result:
Skill file after the turn:
```markdown
---
name: qa-owner-greeting
description: Greets the owner with a fixed marker
---
When the user asks for the owner greeting, reply with exactly: OWNER-GREETING-V2
```
Agent reply: "Let me find the skill file. Done. The `qa-owner-greeting` skill now replies `OWNER-GREETING-V2` instead of `OWNER-GREETING-V1`."
The model edited the skill file in place and confirmed it; it did not refuse or file a Workshop proposal.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(setup): preserve baseline skip-bootstrap intent
Persist the existing skip-bootstrap option before initial workspace creation and keep explicit local defaults out of shared include files. Preserve configured values, user-authored files and include ownership on later baseline runs.
Related: https://github.com/openclaw/openclaw/pull/115945
* fix(core): preserve setup and subagent model intent
* chore(setup): preserve original PR ancestry after scope split
Keep the reviewed current-main setup reconstruction as the complete candidate tree. Requester inheritance is already supplied by PR149036; the separate model-selection-source proposal remains tracked in issue158760. Retain the original PR commit as a parent instead of rewriting its history.
Related: https://github.com/openclaw/openclaw/pull/115945
Related: https://github.com/openclaw/openclaw/issues/158760
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* refactor(commands): deslop commands
Consolidate command orchestration, Doctor migration traversal, status projection, and migration selection while preserving CLI contracts. Repair malformed legacy media rows and align inference owner fingerprints with runtime artifact selection.
* fix(commands): handle duplicate session lifecycle keys
Normalize session keys once before describe and mutation so repeated arguments cannot cause false partial failures. Preserve public error formatting and optional auth fields while resolving the final cleanup lint findings.
* fix(plugins): keep artifact identity on lightweight runtime state
Read the existing bound artifact preference through the canonical runtime-state getter so extracted maintainer wrappers do not import runtime preparation and logging. Preserve the current writer, selection policy, and wrapper inventory.
* fix(update): preserve databases across failed migrations
Include unregistered agent databases in consistent backups and retain database
snapshots with package recovery artifacts before Doctor migrations. Restore only
when recorded write fingerprints still match, preserving displaced migrated
files and refusing silent rollback over unaccounted writes.
Keep pre-Doctor install failures on the existing package recovery path.
Closes#157846
Refs #145169#156917#157107#157077
* fix(update): drain captured database aliases before rollback
* fix(update): isolate database write receipts from orchestration
* fix(update): preserve schema-neutral package rollback
* refactor(update): separate candidate validation and inspection cleanup
* fix(startup): keep legacy state repair in doctor
Separate current-state startup readiness from explicit Doctor migrations.
Preserve current config recovery, quarantine, device identity checks,
lease fencing and updater completion ownership.
Remove automatic startup migration/checkpoint and node-host import paths.
Keep shipped read-only context and roster projections unchanged.
* test(doctor): align proof callers with repair ownership
Keep survivor fixture setup in caller order and select the shared Doctor
flow separately from the channel-specific proof. Include the complete
isolated diagnostics dependency closure.
Prove ordinary startup preserves legacy directories before explicit
Doctor repair, and await SQLite worker closure before test cleanup.
* test(startup): verify index repair through gateway maintenance
* test(doctor): align cutover fixtures with state owners
Reacquire the database after Doctor retires its generation, retain real session history for startup refusal, and keep pending reads at their actual owners. Distinguish the reused readonly reader from independent snapshot-token children and verify every child settles.
* fix(doctor): preserve image activation and published migration receipts
Run the shared noninteractive Doctor owner before default and Compose Gateway
activation so retained Docker volumes keep their published upgrade path while
ordinary Gateway startup stays readiness-only. Preserve root selectors and
settle interrupted repair before executing the original command.
Retain the path-wide tombstones emitted by the published restart-sentinel
importer, including consumed notices, and validate completed source decisions
before retiring recreated inputs. Add the root-image activation lane and causal
receipt coverage without introducing a schema, option, or second importer.
* test(docker): preserve release state pairs in upgrade proof
Use the existing synthetic v2026.9.2 corpus instead of combining a July shared database with a later agent schema. Preserve its deletion journal, registry, both transcript payloads, paired backups, and unsafe-state controls. Register the two shell-launched helpers with Knip.
* test(docker): normalize persisted schema snapshot rows
Compare node:sqlite schema rows using the same plain-object representation as the saved JSON preimage. Keep strict schema, row, and value-type assertions intact.
* fix(models): retain discovered models after refresh failures
Record successful legacy catalog results at the producer boundary so unavailable refreshes retain the accepted inventory. Preserve explicit outcomes, advisory SDK fallback behavior, and first-discovery starter policy.
* fix(models): preserve skipped catalog outcome semantics
Mark bundled static, configured, and advisory catalog projections with
explicit empty outcomes so legacy success inference cannot promote them
to observed account inventory. Preserve live outcomes and helper types.
Keep exact auth provenance histories and move existing fixture/policy
code into focused owners where required by the line-cap ratchet.
Validation: 447 producer and sibling cases, 56 shared self-hosted cases,
95 auth/policy cases, causal missing-outcome failures, maintained checks,
and independent review.
* test(plugin-sdk): keep discovery loader types acyclic
Move the shared loader type into a leaf consumed by both discovery
contract helpers. Preserve its public provider-test-contracts export
without a child-to-parent type import cycle.
Validation: maintained Madge check reports zero cycles; core, all core
test graphs, extension test types, lint, formatting and independent
review pass. Runtime behavior and previous catalog proof are unchanged.
* fix(plugin-sdk): mark generated static catalogs explicitly
Keep the generated non-live, non-strict catalog adapter from claiming
successful acquisition for manifest or configured rows. Preserve null,
errors, strict and custom callbacks, static catalogs, and public types.
Validation: three existing controls fail before the correction; all49
owner and sibling cases pass afterward, with types, lint, line caps and
fresh independent review clean.
* test(gateway): cover restart import during state retirement
Exercise canonical database close while a legacy notice read is paused. Verify admission rejection, retained canonical and source bytes, no migration receipt, and joined maintenance custody without changing the accepted sidecar-stop drain contract.
* fix(test): drain sharing fixtures before removing state
* test: bind retirement regression to its own worker
* docs(docker): clarify automatic Doctor activation
* test(doctor): keep readiness fixture runtime private
* test: stabilize shared skill watcher fixture roots
(cherry picked from commit 15606be10e)
* perf(tooling): share indexed scope parsing for artifact scans
(cherry picked from commit 6e6eef9f5b)
* fix(team-reports): use source owners in scheduler tests
The source barrel retired in #157819, but the scheduler tests still imported
it, breaking the extension test typecheck on main. Import the Discord and
GitHub owners directly, matching the production caller.
Validated the original TS2307/TS7006 failure, the corrected extension type
graph, all 36 scheduler tests, changed checks, and independent P2 review.
(cherry picked from commit 7c4866c73b)
* test(install): isolate global npm configuration in version fixtures
Use a controlled absent global config inside the fixture home so the
release helper does not query the deliberately narrow npm stub. Keep
predecessor selection, fresh-install behavior, and expected exits intact.
(cherry picked from commit 0a9eefc76c)
* test(docker): verify the complete image activation entrypoint