fix: reduce log level for narrative session cleanup scope failures#68130
fix: reduce log level for narrative session cleanup scope failures#68130Linux2010 wants to merge 4 commits into
Conversation
Fixes openclaw#67949 OpenClaw was sending a 'think' parameter to Ollama for all models when thinkingLevel was set, but some smaller models like qwen2.5:0.5b do not support thinking mode, causing a 400 error: "does not support thinking". The fix uses isReasoningModelHeuristic() to detect if a model supports thinking (based on model ID patterns like 'r1', 'reasoning', 'think', 'reason') and only sends the think parameter for models that are known to support it. Changes: - Import isReasoningModelHeuristic from provider-models.js - Check supportsThinking before applying the think wrapper - Update tests to use reasoning model IDs (deepseek-r1:8b) - Add new tests for non-reasoning models (qwen2.5:0.5b)
…y resolution The PROVIDER_ENV_API_KEY_CANDIDATES export was calling resolveProviderEnvApiKeyCandidates() at module load time, which triggered loadPluginManifestRegistry() before bundled plugins were fully available. This caused OpenRouter and other bundled provider auth env vars to be missing from the candidate map at startup. Fixes openclaw#67989: OpenRouter provider not routing requests to models The fix delegates to the existing lazy PROVIDER_AUTH_ENV_VAR_CANDIDATES proxy from provider-env-vars.ts, which defers manifest registry loading until first property access. This ensures bundled plugin metadata (including providerAuthEnvVars from manifests like OpenRouter's) is available when needed. Tests added to verify: - openrouter env var resolution works - lazy loading behavior is preserved
Addresses review feedback from Codex/Greptile/Aisle on PR openclaw#67958
…out operator.admin scope When the dreaming cron runs in the main session, it lacks operator.admin scope required by subagent.deleteSession(). This is expected behavior, not an error condition. Changed the cleanup failure log from warn to debug level when the error message contains 'missing scope: operator.admin', making this best-effort cleanup less noisy in logs while still surfacing genuine errors at warn. Fixes openclaw#68074
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟡 Prototype pollution via untrusted plugin `providerAuthEnvVars` keys in provider env-var candidate resolution
Description
Vulnerable code: const candidates: Record<string, string[]> = {};
...
for (const [providerId, keys] of Object.entries(plugin.providerAuthEnvVars) ...) {
appendUniqueEnvVarCandidates(candidates, providerId, keys);
}
...
const bucket = (target[normalizedProviderId] ??= []);RecommendationHarden map construction against prototype pollution:
Secure example: const candidates: Record<string, string[]> = Object.create(null);
function isSafeKey(k: string): boolean {
return k !== "__proto__" && k !== "prototype" && k !== "constructor";
}
function appendUniqueEnvVarCandidates(
target: Record<string, string[]>,
providerId: string,
keys: readonly string[],
) {
const normalizedProviderId = providerId.trim();
if (!normalizedProviderId || !isSafeKey(normalizedProviderId) || keys.length === 0) return;
const bucket = (target[normalizedProviderId] ??= []);
...
}Analyzed PR: #68130 at commit Last updated on: 2026-04-17T13:48:00Z |
Greptile SummaryThis PR bundles three independent fixes: (1) reduces the dreaming narrative cleanup log from
Confidence Score: 5/5Safe to merge; all findings are non-blocking style/quality suggestions. Both inline findings are P2: the lazy-loading test has a misplaced assertion (doesn't block functionality, only weakens the regression guard), and the string-based scope check is fragile but the worst-case degradation is higher log verbosity. The implementation changes themselves are correct. src/agents/model-auth-env-vars.test.ts — the "is lazy-loaded" test assertion needs to be repositioned after Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/model-auth-env-vars.test.ts
Line: 42
Comment:
**Lazy-load assertion runs before module initializes**
`import()` always resolves asynchronously (Promise microtask), so the module's top-level code hasn't executed yet when this synchronous assertion runs. This means the test passes even if `model-auth-env-vars.ts` eagerly called `loadPluginManifestRegistry` at import time — it wouldn't be caught because the module hasn't been evaluated by the time this check executes.
Move the "not called" assertion to after the `await modPromise` to actually verify the lazy-loading property:
```suggestion
const mod = await modPromise;
// Module is now fully initialized; registry should NOT have been called yet
expect(loadPluginManifestRegistry).not.toHaveBeenCalled();
// Now access the lazy export
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/memory-core/src/dreaming-narrative.ts
Line: 938
Comment:
**Fragile string-based scope detection**
Matching on `errMsg.includes("missing scope: operator.admin")` couples the suppression logic to the exact error message format. If the auth layer ever changes that string (e.g. to `"scope missing: operator.admin"` or adds localization), this silently degrades back to the warn-level noise the PR is trying to eliminate.
If the error type that `deleteSession` throws has a structured code or scope field (e.g. `err.code`, `err.scope`, or an `isScopeError()` helper), prefer checking that instead. If no typed path exists today, a narrow inline comment explaining the message format's stability helps future readers understand the coupling.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(memory-core): reduce log noise for n..." | Re-trigger Greptile |
| const modPromise = import("./model-auth-env-vars.js"); | ||
|
|
||
| // The manifest registry should NOT have been called yet | ||
| expect(loadPluginManifestRegistry).not.toHaveBeenCalled(); |
There was a problem hiding this comment.
Lazy-load assertion runs before module initializes
import() always resolves asynchronously (Promise microtask), so the module's top-level code hasn't executed yet when this synchronous assertion runs. This means the test passes even if model-auth-env-vars.ts eagerly called loadPluginManifestRegistry at import time — it wouldn't be caught because the module hasn't been evaluated by the time this check executes.
Move the "not called" assertion to after the await modPromise to actually verify the lazy-loading property:
| expect(loadPluginManifestRegistry).not.toHaveBeenCalled(); | |
| const mod = await modPromise; | |
| // Module is now fully initialized; registry should NOT have been called yet | |
| expect(loadPluginManifestRegistry).not.toHaveBeenCalled(); | |
| // Now access the lazy export |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-auth-env-vars.test.ts
Line: 42
Comment:
**Lazy-load assertion runs before module initializes**
`import()` always resolves asynchronously (Promise microtask), so the module's top-level code hasn't executed yet when this synchronous assertion runs. This means the test passes even if `model-auth-env-vars.ts` eagerly called `loadPluginManifestRegistry` at import time — it wouldn't be caught because the module hasn't been evaluated by the time this check executes.
Move the "not called" assertion to after the `await modPromise` to actually verify the lazy-loading property:
```suggestion
const mod = await modPromise;
// Module is now fully initialized; registry should NOT have been called yet
expect(loadPluginManifestRegistry).not.toHaveBeenCalled();
// Now access the lazy export
```
How can I resolve this? If you propose a fix, please make it concise.| // Best-effort cleanup: missing operator.admin scope is expected when running | ||
| // in main session (dreaming cron), so log at debug level instead of warn. | ||
| const errMsg = formatErrorMessage(cleanupErr); | ||
| if (errMsg.includes("missing scope: operator.admin")) { |
There was a problem hiding this comment.
Fragile string-based scope detection
Matching on errMsg.includes("missing scope: operator.admin") couples the suppression logic to the exact error message format. If the auth layer ever changes that string (e.g. to "scope missing: operator.admin" or adds localization), this silently degrades back to the warn-level noise the PR is trying to eliminate.
If the error type that deleteSession throws has a structured code or scope field (e.g. err.code, err.scope, or an isScopeError() helper), prefer checking that instead. If no typed path exists today, a narrow inline comment explaining the message format's stability helps future readers understand the coupling.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/dreaming-narrative.ts
Line: 938
Comment:
**Fragile string-based scope detection**
Matching on `errMsg.includes("missing scope: operator.admin")` couples the suppression logic to the exact error message format. If the auth layer ever changes that string (e.g. to `"scope missing: operator.admin"` or adds localization), this silently degrades back to the warn-level noise the PR is trying to eliminate.
If the error type that `deleteSession` throws has a structured code or scope field (e.g. `err.code`, `err.scope`, or an `isScopeError()` helper), prefer checking that instead. If no typed path exists today, a narrow inline comment explaining the message format's stability helps future readers understand the coupling.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3cae1ff2fa
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| params.logger.debug( | ||
| `memory-core: narrative session cleanup skipped (no operator.admin scope) for ${params.data.phase} phase`, |
There was a problem hiding this comment.
Guard debug logging in cleanup scope-error branch
The new missing-scope branch calls params.logger.debug(...), but this function’s Logger contract only requires info/warn/error. In environments that pass a minimal logger (which already matches this file’s declared type), a missing scope: operator.admin cleanup error will now throw TypeError: ...debug is not a function inside finally, turning best-effort cleanup into a hard failure and skipping the remaining scrub path.
Useful? React with 👍 / 👎.
|
Related work from PRtags group Title: Open PR duplicate: [Bug]: memory-core: narrative session cleanup fails with "missing scope: operator.admin"
|
|
Closing this as duplicate or superseded after Codex automated review. Close #68130 as duplicate/superseded. It targets the same memory-core Best possible solution: Close #68130 as a duplicate/superseded contributor PR. Keep the remaining cleanup-warning policy on the canonical maintainer item #68020 or a new focused PR that preserves the local logger contract, keeps genuine cleanup failures visible, and splits unrelated Ollama/model-auth fixes into separate reviewed PRs. What I checked:
So I’m closing this here and keeping the remaining discussion on the canonical linked item. Codex Review notes: model gpt-5.5, reasoning high; reviewed against d54d2d6b9b8a. |
Summary
The
memory-coredreaming feature was generating spurious warning logs every time it ran, stating "missing scope: operator.admin". This occurred because narrative session cleanup callssubagent.deleteSession()which requiresoperator.adminscope, but the dreaming cron runs in the main session without this scope.Changes
dreaming-narrative.tsto check for expected scope failuresoperator.adminscope (expected, best-effort cleanup)Testing
Fixes #68074