Skip to content

cron: add script payload kind for native deterministic job execution#25

Merged
Arry8 merged 8 commits intomainfrom
feature/11-cron-script-payload
Mar 15, 2026
Merged

cron: add script payload kind for native deterministic job execution#25
Arry8 merged 8 commits intomainfrom
feature/11-cron-script-payload

Conversation

@Arry8
Copy link
Copy Markdown
Owner

@Arry8 Arry8 commented Mar 15, 2026

Summary

  • Problem: Cron jobs requiring deterministic, LLM-free execution (scripts, data pipelines, system tasks) had no native path — workarounds used agent turns with prompts like "run this command", which burns tokens and introduces nondeterminism.
  • Why it matters: A general-purpose native execution path means any cron job that doesn't need AI reasoning can run as a first-class script with predictable behavior, proper exit codes, timeouts, and env control.
  • What changed: Added payload.kind: "script" — a new cron payload type that runs a command via child_process.execFile (shell injection-safe), wires script stdout as deliverable output to channels, extends the dashboard UI with script-specific form fields, and trims whitespace in normalize for consistency.
  • What did NOT change: Agent turn, system event, and hook infrastructure are untouched. Delivery routing reuses existing resolveDeliveryTarget + deliverOutboundPayloads infrastructure.

AI-assisted: implemented with Claude Code

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • New CLI flag: openclaw cron add --script <path> [--args <a> <b>] [--env KEY=VAL] [--cwd <dir>] [--timeout <s>] [--announce]
  • Dashboard cron form now includes a "Script" payload kind option with command, args, env, and cwd fields.
  • Script jobs with --announce route stdout summary to the configured delivery channel.
  • Script stdout is capped at ~2 KB; truncated output is indicated in the delivered summary.

Security Impact (required)

  • New permissions/capabilities? YesexecFile runs an arbitrary command path the user specifies in the job config. execFile (not exec) is used explicitly to prevent shell injection — command and args are passed as separate argv, no shell interpolation.
  • Secrets/tokens handling changed? No
  • New/changed network calls? No (delivery reuses existing outbound pipeline)
  • Command/tool execution surface changed? Yes — see above; limited to commands the gateway process owner can already run.
  • Data access scope changed? No
  • Mitigation: execFile argv split prevents shell metacharacter injection. Command path is stored verbatim in the job record (same trust boundary as existing system event payloads). No new network surface.

Repro + Verification

Environment

  • OS: macOS 15 (darwin)
  • Runtime: Node 22+
  • Model/provider: n/a (script jobs bypass LLM)
  • Integration/channel: iMessage / Discord (delivery path)

Steps

  1. pnpm build && pnpm check && pnpm test -- src/cron/exec-script.test.ts src/cron/hooks.test.ts
  2. CLI create: openclaw cron add --name test-script --every 1m --script ~/.openclaw/workspace/scripts/test.cjs --announce --channel last
  3. Verify job runs and stdout is delivered to the configured channel.
  4. Open gateway dashboard → Cron → Add Job → select payload kind "Script" → fill command/args/env → save.
  5. Verify script job appears in job list with correct payload display.

Summary by CodeRabbit

  • New Features

    • Script-based cron jobs: run commands with args/env/cwd/timeout, optional announce delivery, CLI and UI support, and gateway announce handler.
    • Hooks & runtime now expose the job payload to hooks and startup/catchup paths.
  • Bug Fixes

    • Stricter validation and clearer errors for script vs. other payloads, delivery flags, and session restrictions.
  • Tests

    • Comprehensive script execution tests covering outputs, args/env, path resolution, aborts, and missing scripts.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 15, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8beab5ae-cb23-4b9c-a7a3-8ecb56a9a56c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new cron payload kind "script" with native execFile execution, CLI/UI support for script payloads, hook/context payload wiring, delivery integration for script output, type/schema updates, and unit tests for script execution behavior.

Changes

Cohort / File(s) Summary
Script execution core
src/cron/exec-script.ts, src/cron/exec-script.test.ts
New execCronScript API: resolve/expand paths, validate existence, infer interpreter, spawn via execFile (no shell), merge env, enforce cwd/timeout, abort handling (SIGTERM→SIGKILL), capture stdout/stderr; comprehensive tests for success, failure, args, env, path resolution, and aborts.
Types & schema
src/cron/types.ts, src/gateway/protocol/schema/cron.ts, ui/src/ui/types.ts
Adds CronScriptPayload type and patch variant; extends CronPayload/CronPayloadPatch unions; adds JSON schema and patch schema for script payloads and updates UI types.
Service & execution flow
src/cron/service/timer.ts, src/cron/service/jobs.ts, src/cron/service/normalize.ts, src/cron/service/state.ts, src/cron/service/ops.ts
Exec path for payload.kind === "script" calls execCronScript and returns outcome; merges/validates script payloads in job ops; trims script fields in normalization; adds optional announceScriptOutput dep and includes cloned payload in hook/ops contexts.
Hooks & context
src/cron/hooks.ts, src/cron/hooks.test.ts
Adds payload: CronPayload to CronHookContext; adjusts hook module path resolution (expand ~ then resolve relative to basePath); tests updated to exercise script payload visibility and hook behavior.
CLI
src/cli/cron-cli/register.cron-add.ts
Adds --script, --script-arg, --script-env, --script-cwd options and parsing/validation; script becomes a mutually exclusive payload; tightens session/delivery rules and requires --announce when delivery-related flags are used.
Gateway delivery
src/gateway/server-cron.ts
Adds announceScriptOutput handler wired into CronService to resolve agent/context and perform announce-mode deliveries for script stdout, returning delivered/deliveryAttempted flags.
UI — form, controller, views, presenter, defaults, i18n
ui/src/ui/ui-types.ts, ui/src/ui/controllers/cron.ts, ui/src/ui/views/cron.ts, ui/src/ui/presenter.ts, ui/src/ui/app-defaults.ts, ui/src/i18n/locales/en.ts
Adds script payloadKind to form state, new fields (scriptCommand, scriptArgs, scriptEnv, scriptCwd), validation/messages, form rendering and mapping (job↔form), presenter formatting, defaults, and i18n strings.
Normalization
src/cron/normalize.ts
Trims command and cwd for script payloads during coercePayload, removing whitespace-only values.
Misc / config
.coderabbit.yaml
Removes disabled tools.web_search configuration block.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'cron: add script payload kind for native deterministic job execution' clearly and concisely describes the main change: adding a new script payload type for cron jobs to enable direct command execution without LLM processing.
Description check ✅ Passed The PR description is comprehensive and well-structured, addressing the problem, rationale, changes made, security implications, reproduction steps, and verification evidence—all required sections are covered with sufficient detail.
Linked Issues check ✅ Passed The PR successfully implements all primary acceptance criteria from issue #11: new CronScriptPayload type, execFile-based execution, path resolution, stdout/stderr capture, timeout enforcement, file validation, delivery integration, CLI flags, schema updates, and comprehensive tests.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with issue #11 requirements and the stated PR objectives. No unrelated changes detected; all modifications support script payload implementation, CLI integration, UI updates, and testing infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/11-cron-script-payload
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/cli/cron-cli/register.cron-add.ts (1)

249-257: Simplify ternary for session target inference.

The nested ternary can be simplified since both agentTurn and script map to "isolated".

♻️ Suggested simplification
-          const inferredSessionTarget =
-            payload.kind === "agentTurn"
-              ? "isolated"
-              : payload.kind === "script"
-                ? "isolated"
-                : "main";
+          const inferredSessionTarget =
+            payload.kind === "systemEvent" ? "main" : "isolated";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/cron-cli/register.cron-add.ts` around lines 249 - 257, The nested
ternary creating inferredSessionTarget is verbose because both payload.kind
values "agentTurn" and "script" map to "isolated"; replace the nested ternary in
inferredSessionTarget with a single condition that checks if payload.kind ===
"agentTurn" || payload.kind === "script" and returns "isolated", otherwise
"main", and keep subsequent sessionTarget assignment (using sessionSource,
sessionTargetRaw) unchanged; update the symbol inferredSessionTarget and ensure
logic around sessionTarget and sessionSource remains identical.
src/cron/exec-script.ts (1)

38-39: Consider validating cwd directory existence.

If payload.cwd resolves to a non-existent directory, execFile will fail with a less clear error (e.g., ENOENT). Validating upfront would provide a clearer message.

🛡️ Optional validation for cwd
   const resolvedCwd = payload.cwd ? resolveScriptPath(payload.cwd, basePath) : basePath;
+  if (payload.cwd && !fs.existsSync(resolvedCwd)) {
+    return {
+      status: "error",
+      error: `script cwd not found: ${resolvedCwd} (cwd: ${payload.cwd})`,
+    };
+  }
   const childEnv = payload.env ? { ...process.env, ...payload.env } : process.env;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cron/exec-script.ts` around lines 38 - 39, resolvedCwd may point to a
non-existent path causing execFile to fail with unclear ENOENT; after computing
resolvedCwd (using resolveScriptPath(payload.cwd, basePath)), validate that it
exists and is a directory (e.g., fs.stat or fs.existsSync + isDirectory) before
calling execFile, and if the check fails, throw or return a descriptive error
indicating the invalid cwd (include payload.cwd and resolvedCwd) so callers of
exec-script (and logs) get a clear message; keep childEnv logic unchanged.
src/cron/exec-script.test.ts (1)

98-111: Potential test flakiness with fixed timeout.

The 50ms delay before aborting may be too short on slow CI runners, potentially causing the test to abort before the process fully starts, which could lead to intermittent failures.

♻️ Consider a more robust approach
   it("kills script and returns error when aborted mid-run", async () => {
-    const script = writeScript("slow.sh", "#!/bin/sh\nsleep 30");
+    // Write a script that signals readiness before sleeping
+    const script = writeScript("slow.sh", "#!/bin/sh\necho started\nsleep 30");
     const controller = new AbortController();
     const resultPromise = execCronScript({
       payload: { kind: "script", command: script },
       basePath: tmpDir,
       abortSignal: controller.signal,
     });
-    // Abort after a short delay to let the process start.
-    setTimeout(() => controller.abort(), 50);
+    // Give the process time to start; 100ms is more reliable across CI environments
+    setTimeout(() => controller.abort(), 100);
     const result = await resultPromise;
     expect(result.status).toBe("error");
     expect(result.error).toContain("aborted");
   });

Alternatively, you could poll for process existence or use a more sophisticated synchronization mechanism, but increasing the timeout is the simplest fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cron/exec-script.test.ts` around lines 98 - 111, The test "kills script
and returns error when aborted mid-run" uses a fixed 50ms setTimeout to abort
which can be flaky on slow CI; update the test that calls execCronScript (and
uses writeScript, AbortController, tmpDir) to wait more robustly before aborting
— either poll/wait until the child process is confirmed started (e.g., detect
the process or a readiness marker) or simply increase the abort delay to a safer
value (e.g., 500ms) to avoid aborting before the script actually begins.
src/cron/service/timer.ts (1)

1193-1218: Script execution path looks well-structured; consider logging delivery failures.

The script payload execution logic correctly bypasses session/LLM flows and integrates with the existing delivery pipeline. However, the empty catch block on line 1212 silently swallows delivery errors, which could make troubleshooting difficult.

🔧 Proposed fix to log delivery errors
       try {
         const deliveryResult = await state.deps.announceScriptOutput({
           job,
           text: scriptResult.summary,
         });
         return { ...scriptResult, ...deliveryResult };
-      } catch {
+      } catch (err) {
+        state.deps.log.warn(
+          { jobId: job.id, err: String(err) },
+          "cron: script output delivery failed",
+        );
         return { ...scriptResult, delivered: false, deliveryAttempted: true };
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cron/service/timer.ts` around lines 1193 - 1218, The empty catch in the
script payload delivery path swallows errors from announceScriptOutput; update
the catch to capture the thrown error and log it via the existing logging
mechanism (e.g., use state.deps.logger or an available logger) and still return
{ ...scriptResult, delivered: false, deliveryAttempted: true }; locate the block
handling script payloads around execCronScript and the announceScriptOutput call
(refer to job.payload, scriptResult, and state.deps.announceScriptOutput) and
add error capture and a descriptive process/log call including the job id and
error details.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cron/normalize.ts`:
- Around line 97-102: The normalization branch for next.command should treat
whitespace-only strings as missing; after trimming the string in
src/cron/normalize.ts (the block that currently checks typeof next.command ===
"string" and computes trimmed), if trimmed is non-empty set next.command to
trimmed, otherwise remove the property (or set it to undefined) so downstream
validation sees it as absent; update the branch that references next.command to
delete next.command when trimmed is empty.

In `@src/cron/types.ts`:
- Around line 102-105: The CronPayloadPatch union allows
Partial<CronScriptPayload> which makes the discriminant kind optional, but the
runtime expects kind: "script"; update the script variant to require kind:
"script" while keeping other fields optional by replacing
Partial<CronScriptPayload> with (Partial<Omit<CronScriptPayload, "kind">> & {
kind: "script" }) so the type aligns with the runtime discriminated schema;
refer to CronPayloadPatch, CronScriptPayload, and the kind property when making
this change.

In `@src/gateway/server-cron.ts`:
- Around line 305-334: The announceScriptOutput handler should catch errors from
deliverOutboundPayloads so a thrown exception doesn't prevent returning a
delivery status; wrap the await deliverOutboundPayloads call in a try/catch, on
error call cronLogger.error with context (e.g., { jobId: job.id, err:
String(err) }) and return { delivered: false, deliveryAttempted: true },
otherwise return { delivered: true, deliveryAttempted: true }; reference
announceScriptOutput, deliverOutboundPayloads, createOutboundSendDeps and
cronLogger when applying the fix.

---

Nitpick comments:
In `@src/cli/cron-cli/register.cron-add.ts`:
- Around line 249-257: The nested ternary creating inferredSessionTarget is
verbose because both payload.kind values "agentTurn" and "script" map to
"isolated"; replace the nested ternary in inferredSessionTarget with a single
condition that checks if payload.kind === "agentTurn" || payload.kind ===
"script" and returns "isolated", otherwise "main", and keep subsequent
sessionTarget assignment (using sessionSource, sessionTargetRaw) unchanged;
update the symbol inferredSessionTarget and ensure logic around sessionTarget
and sessionSource remains identical.

In `@src/cron/exec-script.test.ts`:
- Around line 98-111: The test "kills script and returns error when aborted
mid-run" uses a fixed 50ms setTimeout to abort which can be flaky on slow CI;
update the test that calls execCronScript (and uses writeScript,
AbortController, tmpDir) to wait more robustly before aborting — either
poll/wait until the child process is confirmed started (e.g., detect the process
or a readiness marker) or simply increase the abort delay to a safer value
(e.g., 500ms) to avoid aborting before the script actually begins.

In `@src/cron/exec-script.ts`:
- Around line 38-39: resolvedCwd may point to a non-existent path causing
execFile to fail with unclear ENOENT; after computing resolvedCwd (using
resolveScriptPath(payload.cwd, basePath)), validate that it exists and is a
directory (e.g., fs.stat or fs.existsSync + isDirectory) before calling
execFile, and if the check fails, throw or return a descriptive error indicating
the invalid cwd (include payload.cwd and resolvedCwd) so callers of exec-script
(and logs) get a clear message; keep childEnv logic unchanged.

In `@src/cron/service/timer.ts`:
- Around line 1193-1218: The empty catch in the script payload delivery path
swallows errors from announceScriptOutput; update the catch to capture the
thrown error and log it via the existing logging mechanism (e.g., use
state.deps.logger or an available logger) and still return { ...scriptResult,
delivered: false, deliveryAttempted: true }; locate the block handling script
payloads around execCronScript and the announceScriptOutput call (refer to
job.payload, scriptResult, and state.deps.announceScriptOutput) and add error
capture and a descriptive process/log call including the job id and error
details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f740c64-32cf-4bf5-a980-edda60b903ab

📥 Commits

Reviewing files that changed from the base of the PR and between 9c4eaf6 and 5f3cd231fbee094fccbb9f18167b8e693f9a6354.

⛔ Files ignored due to path filters (1)
  • docs/repo-map.json is excluded by !docs/repo-map.json
📒 Files selected for processing (21)
  • src/cli/cron-cli/register.cron-add.ts
  • src/cron/exec-script.test.ts
  • src/cron/exec-script.ts
  • src/cron/hooks.test.ts
  • src/cron/hooks.ts
  • src/cron/normalize.ts
  • src/cron/service/jobs.ts
  • src/cron/service/normalize.ts
  • src/cron/service/ops.ts
  • src/cron/service/state.ts
  • src/cron/service/timer.ts
  • src/cron/types.ts
  • src/gateway/protocol/schema/cron.ts
  • src/gateway/server-cron.ts
  • ui/src/i18n/locales/en.ts
  • ui/src/ui/app-defaults.ts
  • ui/src/ui/controllers/cron.ts
  • ui/src/ui/presenter.ts
  • ui/src/ui/types.ts
  • ui/src/ui/ui-types.ts
  • ui/src/ui/views/cron.ts

Comment thread src/cron/normalize.ts
Comment thread src/cron/types.ts Outdated
Comment thread src/gateway/server-cron.ts
@Arry8
Copy link
Copy Markdown
Owner Author

Arry8 commented Mar 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/cron/exec-script.ts (2)

6-7: Use import type for type-only imports.

These imports are only used as types, not values. Per coding guidelines, use import type { X } for type-only imports.

Suggested fix
-import type { CronRunOutcome } from "./types.js";
-import type { CronScriptPayload } from "./types.js";
+import type { CronRunOutcome, CronScriptPayload } from "./types.js";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cron/exec-script.ts` around lines 6 - 7, The two imports for
CronRunOutcome and CronScriptPayload are type-only and should use TypeScript's
type-only import syntax; update the import statements that bring in
CronRunOutcome and CronScriptPayload so they read "import type { CronRunOutcome
} from './types.js';" and "import type { CronScriptPayload } from './types.js';"
(ensure there are no value imports of those symbols elsewhere in
exec-script.ts).

136-140: Clarify fallback behavior for TypeScript files.

The comment states "falls back to node+tsx if bun is unavailable," but no fallback logic is implemented—the spawn will fail with a "command not found" error if bun isn't installed. Consider either:

  1. Updating the comment to reflect actual behavior (spawn error surfaced to user)
  2. Adding runtime probing to check for bun availability

The current behavior is acceptable (clear spawn error), but the comment is misleading.

Suggested comment update
     case ".ts":
-      // Prefer bun for TypeScript; falls back to node+tsx if bun is unavailable,
-      // but we let the caller surface that as a spawn error rather than probing.
+      // Uses bun for TypeScript. If bun is not installed, the spawn will fail
+      // with an error message. Users can ensure bun is available or use a shebang.
       return "bun";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cron/exec-script.ts` around lines 136 - 140, The comment in the switch
branch for TypeScript (case ".ts") claims it "falls back to node+tsx if bun is
unavailable" but no such fallback exists; update the comment in exec-script.ts
at the case ".ts" branch (the logic that returns "bun") to accurately state that
we prefer bun and that if bun is not installed the spawn will fail and the
caller will surface the error, or alternatively implement a runtime probe for
bun availability and return "node+tsx" when bun is absent—pick one approach and
make the comment and/or code consistent with that choice.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cron/exec-script.ts`:
- Around line 6-7: The two imports for CronRunOutcome and CronScriptPayload are
type-only and should use TypeScript's type-only import syntax; update the import
statements that bring in CronRunOutcome and CronScriptPayload so they read
"import type { CronRunOutcome } from './types.js';" and "import type {
CronScriptPayload } from './types.js';" (ensure there are no value imports of
those symbols elsewhere in exec-script.ts).
- Around line 136-140: The comment in the switch branch for TypeScript (case
".ts") claims it "falls back to node+tsx if bun is unavailable" but no such
fallback exists; update the comment in exec-script.ts at the case ".ts" branch
(the logic that returns "bun") to accurately state that we prefer bun and that
if bun is not installed the spawn will fail and the caller will surface the
error, or alternatively implement a runtime probe for bun availability and
return "node+tsx" when bun is absent—pick one approach and make the comment
and/or code consistent with that choice.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 03e2c63f-ed21-42ac-b68b-cb6b3305d75e

📥 Commits

Reviewing files that changed from the base of the PR and between 5f3cd231fbee094fccbb9f18167b8e693f9a6354 and ec8818beb8e45222a93341802bb872768ab66fdf.

⛔ Files ignored due to path filters (1)
  • docs/repo-map.json is excluded by !docs/repo-map.json
📒 Files selected for processing (9)
  • .coderabbit.yaml
  • src/cli/cron-cli/register.cron-add.ts
  • src/cron/exec-script.test.ts
  • src/cron/exec-script.ts
  • src/cron/normalize.ts
  • src/cron/service/jobs.ts
  • src/cron/service/timer.ts
  • src/cron/types.ts
  • src/gateway/server-cron.ts
💤 Files with no reviewable changes (1)
  • .coderabbit.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/cron/exec-script.test.ts
  • src/gateway/server-cron.ts

@Arry8
Copy link
Copy Markdown
Owner Author

Arry8 commented Mar 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/cron/exec-script.ts (1)

27-35: Consider validating non-empty command.

If payload.command is empty or whitespace-only, resolveScriptPath will return basePath itself. Since basePath is a directory, fs.existsSync would return true, and execFile would then fail with a confusing error (e.g., EACCES or EISDIR).

🛡️ Proposed fix to add early validation
   const resolvedCommand = resolveScriptPath(payload.command, basePath);
+
+  if (!payload.command.trim()) {
+    return { status: "error", error: "script command is empty" };
+  }

   // Validate file exists before spawning to give a clear error message.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cron/exec-script.ts` around lines 27 - 35, The code fails to validate
that payload.command is non-empty/whitespace and may treat basePath (a
directory) as a valid script because resolveScriptPath returns basePath; update
the pre-check before calling resolveScriptPath/execFile to reject empty or
whitespace-only payload.command and/or ensure the resolved path is a regular
file (not a directory) using fs.stat or similar; specifically add validation
around payload.command and after resolveScriptPath confirm the path is a file
(e.g., using fs.statSync(resolvedCommand).isFile()) and return a clear error
object if the command is empty or the resolved path is not a file so execFile
won’t produce confusing EISDIR/EACCES errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cron/exec-script.ts`:
- Around line 127-130: The switch in exec-script.ts currently maps the case
labels ".bash" and ".zsh" to return "sh", which prevents bash/zsh-specific
features from working; update the case handling so that ".bash" returns "bash"
and ".zsh" returns "zsh" (leave ".sh" returning "sh"), ensuring the interpreter
selection logic (the case ".sh"/".bash"/".zsh" block) uses the correct
interpreter strings instead of defaulting both to "sh".

---

Nitpick comments:
In `@src/cron/exec-script.ts`:
- Around line 27-35: The code fails to validate that payload.command is
non-empty/whitespace and may treat basePath (a directory) as a valid script
because resolveScriptPath returns basePath; update the pre-check before calling
resolveScriptPath/execFile to reject empty or whitespace-only payload.command
and/or ensure the resolved path is a regular file (not a directory) using
fs.stat or similar; specifically add validation around payload.command and after
resolveScriptPath confirm the path is a file (e.g., using
fs.statSync(resolvedCommand).isFile()) and return a clear error object if the
command is empty or the resolved path is not a file so execFile won’t produce
confusing EISDIR/EACCES errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ab0eeed8-428a-4318-8cb8-c99e8f2b6887

📥 Commits

Reviewing files that changed from the base of the PR and between ec8818beb8e45222a93341802bb872768ab66fdf and edab5a686a3d9dd8ce5f41cd704174565604d7cd.

📒 Files selected for processing (1)
  • src/cron/exec-script.ts

Comment thread src/cron/exec-script.ts Outdated
@Arry8 Arry8 marked this pull request as ready for review March 15, 2026 14:28
@Arry8
Copy link
Copy Markdown
Owner Author

Arry8 commented Mar 15, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 15, 2026

✅ Actions performed

Full review triggered.

@Arry8 Arry8 force-pushed the feature/11-cron-script-payload branch from 2e57f9b to 920ce3a Compare March 15, 2026 14:34
@Arry8 Arry8 merged this pull request into main Mar 15, 2026
1 check passed
@Arry8 Arry8 deleted the feature/11-cron-script-payload branch March 15, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement]: cron: native script execution payload (bypass LLM for deterministic jobs)

1 participant