fix(agents): narrow session lock scope

This commit is contained in:
Peter Steinberger
2026-04-27 09:08:35 +01:00
parent 5ff49ae03e
commit 5b616e2bec
3 changed files with 35 additions and 10 deletions

View File

@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/sessions: acquire the session write lock only after cold bootstrap, plugin, and tool setup so fallback runs are not blocked by stalled pre-model startup work. Thanks @codex.
- Gateway/models: skip external OpenRouter and LiteLLM pricing refreshes for local/self-hosted model endpoints so startup does not wait on remote pricing catalogs for local-only Ollama, vLLM, and compatible providers. Thanks @codex.
- CLI/plugins: stop security-blocked plugin installs from retrying as hook packs, so normal plugin packages report the scanner failure without a misleading "not a valid hook pack" follow-up. Fixes #61175; supersedes #64102. Thanks @KonsultDigital and @ziyincody.
- Control UI/Dreaming: require explicit confirmation before applying restart-impacting Dreaming mode changes, with restart warning copy and loading feedback. Fixes #63804. (#63807) Thanks @bbddbb1.

View File

@@ -253,6 +253,27 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
expectCalledWithSessionKey(afterTurn, sessionKey);
});
it("resolves bootstrap context before acquiring the session write lock", async () => {
const events: string[] = [];
hoisted.resolveBootstrapContextForRunMock.mockImplementation(async () => {
events.push("bootstrap");
return { bootstrapFiles: [], contextFiles: [] };
});
hoisted.acquireSessionWriteLockMock.mockImplementation(async () => {
events.push("lock");
return { release: async () => {} };
});
await createContextEngineAttemptRunner({
contextEngine: createContextEngineBootstrapAndAssemble(),
sessionKey,
tempPaths,
});
expect(events).toEqual(expect.arrayContaining(["bootstrap", "lock"]));
expect(events.indexOf("bootstrap")).toBeLessThan(events.indexOf("lock"));
});
it("forwards modelId to assemble", async () => {
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
const contextEngine = createTestContextEngine({ bootstrap, assemble });

View File

@@ -639,16 +639,6 @@ export async function runEmbeddedAttempt(
agentId: sessionAgentId,
});
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
maxHoldMs: resolveSessionLockMaxHoldFromTimeout({
timeoutMs: resolveRunTimeoutWithCompactionGraceMs({
runTimeoutMs: params.timeoutMs,
compactionTimeoutMs: resolveCompactionTimeoutMs(params.config),
}),
}),
});
const sessionLabel = params.sessionKey ?? params.sessionId;
const contextInjectionMode = resolveContextInjectionMode(params.config);
const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
@@ -1205,6 +1195,19 @@ export async function runEmbeddedAttempt(
let systemPromptText = systemPromptOverride();
const userPromptPrefixText = bootstrapRouting.userPromptPrefixText;
// Keep the session lock scoped to transcript/session mutations. Cold plugin
// and tool setup can be slow, and holding the lock there blocks CLI fallback
// from taking over the same session when a gateway run stalls before model I/O.
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
maxHoldMs: resolveSessionLockMaxHoldFromTimeout({
timeoutMs: resolveRunTimeoutWithCompactionGraceMs({
runTimeoutMs: params.timeoutMs,
compactionTimeoutMs: resolveCompactionTimeoutMs(params.config),
}),
}),
});
let sessionManager: ReturnType<typeof guardSessionManager> | undefined;
let session: Awaited<ReturnType<typeof createAgentSession>>["session"] | undefined;
let removeToolResultContextGuard: (() => void) | undefined;