Skip to content

Commit 686b93e

Browse files
authored
fix: keep command cron turns lightweight
1 parent e575325 commit 686b93e

7 files changed

Lines changed: 74 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
2020

2121
### Fixes
2222

23+
- Cron/Codex: default exact-command scheduled agent turns to lightweight bootstrap context so automation runs the command before loading workspace identity or memory context.
2324
- Codex plugin/Gateway: strip unpaired UTF-16 surrogates from Codex app-server JSON-RPC payloads and let stale reply-work recovery abort stalled reply runs, preventing malformed media turns from wedging gateway lanes.
2425
- Codex app server: force OAuth refresh requests to perform a real token refresh instead of reusing unchanged inherited auth-profile tokens after refresh failures. (#80738) Thanks @simplyclever914.
2526
- Bind gateway approval access to requester metadata [AI]. (#81380) Thanks @pgondhi987.

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5899,7 +5899,7 @@ describe("runCodexAppServerAttempt", () => {
58995899
"If it asks you to run an exact command, run that command before doing any investigation",
59005900
);
59015901
expect(cronCollaborationMode.settings.developer_instructions).toContain(
5902-
"Do not read AGENTS.md, SOUL.md, USER.md, PROJECTS.md, MEMORY.md",
5902+
"Use context already provided by the runtime",
59035903
);
59045904
});
59055905

extensions/codex/src/app-server/thread-lifecycle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ function buildCronCollaborationInstructions(): string {
571571
return [
572572
"This is an OpenClaw cron automation turn. Apply these instructions only to this scheduled job; ordinary chat turns should stay in Codex Default mode.",
573573
"Execute the cron payload directly. If it asks you to run an exact command, run that command before doing any investigation, planning, memory review, or workspace bootstrap.",
574-
"Do not read AGENTS.md, SOUL.md, USER.md, PROJECTS.md, MEMORY.md, day logs, entity summaries, or other workspace memory/bootstrap files unless the cron payload explicitly asks you to inspect them or the requested command fails and the file is needed to diagnose that failure.",
574+
"Use context already provided by the runtime, but do not spend time loading or re-reading workspace bootstrap, memory, or project-doc files before executing the cron payload. Inspect those files only if the payload asks for them or the command fails and they are needed to diagnose it.",
575575
"Keep output concise and automation-oriented. Prefer the final command result or a short failure summary over status narration.",
576576
].join("\n\n");
577577
}

src/agents/cli-runner/prepare.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ export async function prepareCliRunContext(
163163
config: params.config,
164164
sessionKey: params.sessionKey,
165165
sessionId: params.sessionId,
166+
contextMode: params.bootstrapContextMode,
167+
runKind: params.bootstrapContextRunKind,
166168
warn: prepareDeps.makeBootstrapWarn({
167169
sessionLabel,
168170
workspaceDir,

src/agents/cli-runner/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { CliBackendConfig } from "../../config/types.js";
88
import type { OpenClawConfig } from "../../config/types.openclaw.js";
99
import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js";
1010
import type { InputProvenance } from "../../sessions/input-provenance.js";
11+
import type { BootstrapContextMode } from "../bootstrap-files.js";
1112
import type { ResolvedCliBackend } from "../cli-backends.js";
1213
import type {
1314
CurrentTurnPromptContext,
@@ -48,6 +49,8 @@ export type RunCliAgentParams = {
4849
authProfileId?: string;
4950
bootstrapPromptWarningSignaturesSeen?: string[];
5051
bootstrapPromptWarningSignature?: string;
52+
bootstrapContextMode?: BootstrapContextMode;
53+
bootstrapContextRunKind?: "default" | "heartbeat" | "cron";
5154
images?: ImageContent[];
5255
imageOrder?: PromptImageOrderEntry[];
5356
skillsSnapshot?: SkillSnapshot;

src/cron/isolated-agent.session-identity.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ setupRunCronIsolatedAgentTurnSuite();
2424

2525
function lastEmbeddedAgentCall(): {
2626
agentDir?: string;
27+
bootstrapContextMode?: "full" | "lightweight";
2728
prompt?: string;
2829
sessionKey?: string;
2930
workspaceDir?: string;
@@ -40,6 +41,7 @@ function lastEmbeddedAgentCall(): {
4041
}
4142
return value as {
4243
agentDir?: string;
44+
bootstrapContextMode?: "full" | "lightweight";
4345
prompt?: string;
4446
sessionKey?: string;
4547
workspaceDir?: string;
@@ -140,6 +142,43 @@ describe("runCronIsolatedAgentTurn session identity", () => {
140142
});
141143
});
142144

145+
it("uses lightweight bootstrap context for command-style cron payloads", async () => {
146+
await withTempHome(async (home) => {
147+
await runCronTurn(home, {
148+
jobPayload: {
149+
kind: "agentTurn",
150+
message: "cd /srv/openclaw && ./scripts/nightly-report.sh",
151+
},
152+
});
153+
154+
expect(lastEmbeddedAgentCall().bootstrapContextMode).toBe("lightweight");
155+
});
156+
});
157+
158+
it("does not force lightweight bootstrap context for natural-language cron payloads", async () => {
159+
await withTempHome(async (home) => {
160+
await runCronTurn(home, {
161+
jobPayload: { kind: "agentTurn", message: "Prepare the nightly status summary" },
162+
});
163+
164+
expect(lastEmbeddedAgentCall().bootstrapContextMode).toBeUndefined();
165+
});
166+
});
167+
168+
it("honors explicit full bootstrap context for command-style cron payloads", async () => {
169+
await withTempHome(async (home) => {
170+
await runCronTurn(home, {
171+
jobPayload: {
172+
kind: "agentTurn",
173+
message: "pnpm run nightly-report",
174+
lightContext: false,
175+
},
176+
});
177+
178+
expect(lastEmbeddedAgentCall().bootstrapContextMode).toBeUndefined();
179+
});
180+
});
181+
143182
it("starts a fresh session id for each cron run", async () => {
144183
await withTempHome(async (home) => {
145184
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });

src/cron/isolated-agent/run-executor.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { BootstrapContextMode } from "../../agents/bootstrap-files.js";
12
import { resolveCliRuntimeExecutionProvider } from "../../agents/model-runtime-aliases.js";
23
import type { SkillSnapshot } from "../../agents/skills.js";
34
import { normalizeToolList } from "../../agents/tool-policy.js";
@@ -60,6 +61,29 @@ function resolveCronOwnerOnlyToolAllowlist(toolsAllow: string[] | undefined): st
6061
return ["cron"];
6162
}
6263

64+
const COMMAND_STYLE_CRON_PREFIX =
65+
/^(?:(?:[A-Z_][A-Z0-9_]*=\S+\s+)+)?(?:cd\s+\S+|(?:\.{1,2}|~)?\/\S+|[A-Za-z]:[\\/]\S+|(?:bash|bun|cargo|deno|docker|gh|git|go|make|node|npm|npx|pnpm|python|python3|ruby|sh|tsx|uv|zsh)\b)/u;
66+
67+
export function isCommandStyleCronMessage(message: string): boolean {
68+
const trimmed = message.trim();
69+
if (!trimmed || trimmed.includes("\n")) {
70+
return false;
71+
}
72+
return COMMAND_STYLE_CRON_PREFIX.test(trimmed);
73+
}
74+
75+
function resolveCronBootstrapContextMode(
76+
payload: AgentTurnPayload,
77+
): BootstrapContextMode | undefined {
78+
if (payload?.lightContext === true) {
79+
return "lightweight";
80+
}
81+
if (payload?.lightContext === false) {
82+
return undefined;
83+
}
84+
return isCommandStyleCronMessage(payload?.message ?? "") ? "lightweight" : undefined;
85+
}
86+
6387
export type CronExecutionResult = {
6488
runResult: CronPromptRunResult;
6589
fallbackProvider: string;
@@ -180,6 +204,8 @@ export function createCronPromptExecutor(params: {
180204
onExecutionPhase: params.onExecutionPhase,
181205
bootstrapPromptWarningSignaturesSeen,
182206
bootstrapPromptWarningSignature,
207+
bootstrapContextMode: resolveCronBootstrapContextMode(params.agentPayload),
208+
bootstrapContextRunKind: "cron",
183209
senderIsOwner: params.senderIsOwner,
184210
});
185211
bootstrapPromptWarningSignaturesSeen = resolveBootstrapWarningSignaturesSeen(
@@ -234,7 +260,7 @@ export function createCronPromptExecutor(params: {
234260
verboseLevel: params.resolvedVerboseLevel,
235261
timeoutMs: params.timeoutMs,
236262
runTimeoutOverrideMs: params.runTimeoutOverrideMs,
237-
bootstrapContextMode: params.agentPayload?.lightContext ? "lightweight" : undefined,
263+
bootstrapContextMode: resolveCronBootstrapContextMode(params.agentPayload),
238264
bootstrapContextRunKind: "cron",
239265
toolsAllow: params.agentPayload?.toolsAllow,
240266
execOverrides: params.suppressExecNotifyOnExit

0 commit comments

Comments
 (0)