Symptom
When the memory-core dreaming pipeline finishes a narrative cycle (cron-driven
or manual), session cleanup fails with:
[memory-core/dreaming] failed to delete dreaming subagent session: missing scope: operator.admin
This recurs on every dreaming run. The narrative completes (memories are
written) but the ephemeral subagent session is never reaped, leaving stale
session records.
Environment
- openclaw
2026.4.24 (also reproduced against current main, commit 0286bb98)
- Linux / WSL Ubuntu-24.04, Node 22
- Plugins enabled:
active-memory, browser, memory-core, memory-wiki, whatsapp
- Cron-driven dreaming (
memory.dreaming.* jobs registered via cron)
- Reproduces with default
cron.enabled: true
Trace
The dreaming worker calls subagent.deleteSession(...) on the plugin runtime.
That hits createGatewaySubagentRuntime().deleteSession in
src/gateway/server-plugins.ts (≈ line 380):
async deleteSession(params) {
await dispatchGatewayMethod("sessions.delete", {
key: params.sessionKey,
deleteTranscript: params.deleteTranscript ?? true,
});
},
dispatchGatewayMethod looks up a request scope via
getPluginRuntimeGatewayRequestScope(). When the dreaming job is invoked from
the cron service / fallback gateway context (not a live operator request), no
scope is set, so dispatchGatewayMethod falls back to
createSyntheticOperatorClient() which mints a client with
scopes: [WRITE_SCOPE].
But sessions.delete is in the ADMIN_SCOPE list
(src/gateway/method-scopes.ts ≈ line 175), so authorization fails with
missing scope: operator.admin.
The existing test asserts this is the intentional behavior:
src/gateway/server-plugins.test.ts
test("rejects fallback session deletion without minting admin scope", …)
…rejects.toThrow("missing scope: operator.admin")
So the gateway side is by design. The contract is: a caller wanting to delete
a session must already be in a withPluginRuntimeGatewayRequestScope whose
client carries operator.admin (the second test, "allows session deletion when
the request scope already has admin", confirms this works).
Where the contract is broken
The memory-core dreaming worker (bundled plugin) fires its cleanup from a
cron callback. It does not appear to wrap the cleanup in
withPluginRuntimeGatewayRequestScope with an admin client, so it inevitably
hits the synthetic operator.write fallback and is rejected.
Possible fixes (need maintainer guidance — not opening a PR yet)
-
Memory-core side: wrap dreaming cleanups in
withPluginRuntimeGatewayRequestScope with an admin-scoped synthetic
client. Memory-core is a trusted bundled plugin, so this is reasonable —
but the wrapping helper isn't currently exposed to plugin code.
-
Plugin runtime side: introduce an opt-in
subagent.deleteSession({ asAdmin: true }) (or a new
subagent.adminDeleteSession) which is gated by an explicit per-plugin
policy (similar to the existing allowModelOverride allowlist in
setPluginSubagentOverridePolicies). This keeps the synthetic-fallback
safety net intact while giving trusted plugins a documented escape hatch.
-
Method-scopes side: split the lifecycle of operator-initiated
session deletes vs. plugin-initiated, plugin-owned session deletes into
two methods, with the plugin variant requiring only operator.write plus
ownership proof.
Workaround
For self-hosted users hitting this on every dreaming run, a local patch to the
distributed dist/server-plugin-bootstrap-*.js to pass
{ syntheticScopes: ["operator.admin"] } from deleteSession will silence
the error — but this is not a correct upstream change because it removes the
security boundary asserted by the test above.
Reproduce
- Enable
memory-core and set cron.enabled: true.
- Trigger a dreaming run (
openclaw cron run memory.dreaming.narrative or
wait for the schedule).
- Observe
journalctl --user -u openclaw-gateway:
[memory-core/dreaming] failed to delete dreaming subagent session: missing scope: operator.admin
Disclosure: Drafted with assistance from GitHub Copilot CLI based on
diagnostics from a self-hosted openclaw deployment.
Symptom
When the
memory-coredreaming pipeline finishes a narrative cycle (cron-drivenor manual), session cleanup fails with:
This recurs on every dreaming run. The narrative completes (memories are
written) but the ephemeral subagent session is never reaped, leaving stale
session records.
Environment
2026.4.24(also reproduced against currentmain, commit0286bb98)active-memory, browser, memory-core, memory-wiki, whatsappmemory.dreaming.*jobs registered viacron)cron.enabled: trueTrace
The dreaming worker calls
subagent.deleteSession(...)on the plugin runtime.That hits
createGatewaySubagentRuntime().deleteSessioninsrc/gateway/server-plugins.ts(≈ line 380):dispatchGatewayMethodlooks up a request scope viagetPluginRuntimeGatewayRequestScope(). When the dreaming job is invoked fromthe cron service / fallback gateway context (not a live operator request), no
scope is set, so
dispatchGatewayMethodfalls back tocreateSyntheticOperatorClient()which mints a client withscopes: [WRITE_SCOPE].But
sessions.deleteis in theADMIN_SCOPElist(
src/gateway/method-scopes.ts≈ line 175), so authorization fails withmissing scope: operator.admin.The existing test asserts this is the intentional behavior:
So the gateway side is by design. The contract is: a caller wanting to delete
a session must already be in a
withPluginRuntimeGatewayRequestScopewhoseclient carries
operator.admin(the second test, "allows session deletion whenthe request scope already has admin", confirms this works).
Where the contract is broken
The
memory-coredreaming worker (bundled plugin) fires its cleanup from acron callback. It does not appear to wrap the cleanup in
withPluginRuntimeGatewayRequestScopewith an admin client, so it inevitablyhits the synthetic
operator.writefallback and is rejected.Possible fixes (need maintainer guidance — not opening a PR yet)
Memory-core side: wrap dreaming cleanups in
withPluginRuntimeGatewayRequestScopewith an admin-scoped syntheticclient. Memory-core is a trusted bundled plugin, so this is reasonable —
but the wrapping helper isn't currently exposed to plugin code.
Plugin runtime side: introduce an opt-in
subagent.deleteSession({ asAdmin: true })(or a newsubagent.adminDeleteSession) which is gated by an explicit per-pluginpolicy (similar to the existing
allowModelOverrideallowlist insetPluginSubagentOverridePolicies). This keeps the synthetic-fallbacksafety net intact while giving trusted plugins a documented escape hatch.
Method-scopes side: split the lifecycle of operator-initiated
session deletes vs. plugin-initiated, plugin-owned session deletes into
two methods, with the plugin variant requiring only
operator.writeplusownership proof.
Workaround
For self-hosted users hitting this on every dreaming run, a local patch to the
distributed
dist/server-plugin-bootstrap-*.jsto pass{ syntheticScopes: ["operator.admin"] }fromdeleteSessionwill silencethe error — but this is not a correct upstream change because it removes the
security boundary asserted by the test above.
Reproduce
memory-coreand setcron.enabled: true.openclaw cron run memory.dreaming.narrativeorwait for the schedule).
journalctl --user -u openclaw-gateway:[memory-core/dreaming] failed to delete dreaming subagent session: missing scope: operator.adminDisclosure: Drafted with assistance from GitHub Copilot CLI based on
diagnostics from a self-hosted openclaw deployment.