fix: avoid cron embedded lane deadlock

This commit is contained in:
Peter Steinberger
2026-03-10 23:56:21 +00:00
parent 8901032007
commit 825a435709
2 changed files with 24 additions and 4 deletions

View File

@@ -35,12 +35,12 @@ function mockEmbeddedOk() {
}
/**
* Extract the provider and model from the last runEmbeddedPiAgent call.
* Extract select fields from the last runEmbeddedPiAgent call.
*/
function lastEmbeddedCall(): { provider?: string; model?: string } {
function lastEmbeddedCall(): { provider?: string; model?: string; lane?: string } {
const calls = vi.mocked(runEmbeddedPiAgent).mock.calls;
expect(calls.length).toBeGreaterThan(0);
return calls.at(-1)?.[0] as { provider?: string; model?: string };
return calls.at(-1)?.[0] as { provider?: string; model?: string; lane?: string };
}
const DEFAULT_MESSAGE = "do it";
@@ -106,6 +106,14 @@ describe("cron model formatting and precedence edge cases", () => {
// ------ provider/model string splitting ------
describe("parseModelRef formatting", () => {
it("moves nested embedded runs off the cron lane to avoid self-deadlock", async () => {
await withTempHome(async (home) => {
const { res, call } = await runTurn(home);
expect(res.status).toBe("ok");
expect(call.lane).toBe("nested");
});
});
it("splits standard provider/model", async () => {
await withTempHome(async (home) => {
const { res, call } = await runTurn(home, {

View File

@@ -46,6 +46,7 @@ import {
import type { AgentDefaultsConfig } from "../../config/types.js";
import { registerAgentRunContext } from "../../infra/agent-events.js";
import { logWarn } from "../../logger.js";
import { CommandLane } from "../../process/lanes.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import {
buildSafeExternalPrompt,
@@ -197,6 +198,17 @@ function appendCronDeliveryInstruction(params: {
return `${params.commandBody}\n\nReturn your summary as plain text; it will be delivered automatically. If the task explicitly calls for messaging a specific external recipient, note who/where it should go instead of sending it yourself.`.trim();
}
function resolveCronEmbeddedAgentLane(lane?: string) {
const trimmed = lane?.trim();
// Cron jobs already execute inside the cron command lane. Reusing that same
// lane for the nested embedded-agent run deadlocks: the outer cron task holds
// the lane while the inner run waits to reacquire it.
if (!trimmed || trimmed === "cron") {
return CommandLane.Nested;
}
return trimmed;
}
export async function runCronIsolatedAgentTurn(params: {
cfg: OpenClawConfig;
deps: CliDeps;
@@ -610,7 +622,7 @@ export async function runCronIsolatedAgentTurn(params: {
config: cfgWithAgentDefaults,
skillsSnapshot,
prompt: promptText,
lane: params.lane ?? "cron",
lane: resolveCronEmbeddedAgentLane(params.lane),
provider: providerOverride,
model: modelOverride,
authProfileId,