MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / runOne

Function runOne

src/schedule/runner.ts:82–174  ·  view source on GitHub ↗
(entry: ScheduleEntry)

Source from the content-addressed store, hash-verified

80}
81
82async function runOne(entry: ScheduleEntry): Promise<void> {
83 const store = getScheduleStore();
84 const startMs = Date.now();
85 const runId = store.recordRunStart(entry.id);
86 const logPath = path.join(RUN_LOG_DIR, `${entry.id}.${runId}.log`);
87 const logStream = fsSync.createWriteStream(logPath, { flags: 'w' });
88 logStream.write(`# schedule: ${entry.name} (${entry.id})\n# cron: ${entry.cron}\n# cwd: ${entry.cwd}\n# started: ${new Date().toISOString()}\n# prompt: ${entry.prompt.replace(/\n/g, '\n# ')}\n\n`);
89
90 // cwd must exist; otherwise mark errored and bail (no point retrying every minute)
91 try {
92 const st = fsSync.statSync(entry.cwd);
93 if (!st.isDirectory()) throw new Error(`not a directory`);
94 } catch (e: any) {
95 const msg = `cwd invalid: ${entry.cwd} (${e.message})`;
96 logStream.end(msg + '\n');
97 store.recordRunFinish(runId, entry.id, 'error', 1, msg, Date.now() - startMs);
98 return;
99 }
100
101 // Build child args: qodex --print <prompt> --yes [--model ...]
102 // We use --yes so permission prompts auto-approve; without it the headless run
103 // would deny everything and the schedule would be useless. A recipe (e.g. verified-pr)
104 // wraps the goal in an unattended-safe protocol before it's fed to the agent.
105 const runPrompt = buildRecipePrompt(entry.recipe, entry.prompt);
106 const args: string[] = ['--print', runPrompt, '--yes'];
107 if (entry.model) { args.push('--model', entry.model); }
108
109 // Resolve the qodex CLI path. In production the user runs `npm link` so `qodex`
110 // is on PATH; in dev we may need to point at bin/qodex.mjs directly.
111 const cliPath = resolveCliPath();
112
113 // Ask the child to write a ground-truth receipt here (built by QodeX from the git diff +
114 // the checkers it ran, not the model). We prefer it over parsing the model's stdout block.
115 const receiptFile = path.join(RUN_LOG_DIR, `${entry.id}.${runId}.receipt.json`);
116
117 return new Promise<void>((resolve) => {
118 const child = spawn(cliPath, args, {
119 cwd: entry.cwd,
120 env: { ...process.env, QODEX_SCHEDULED: '1', QODEX_RECEIPT_FILE: receiptFile },
121 stdio: ['ignore', 'pipe', 'pipe'],
122 });
123
124 let output = '';
125 child.stdout?.on('data', (d: Buffer) => { const s = d.toString(); output += s; logStream.write(s); });
126 child.stderr?.on('data', (d: Buffer) => { const s = d.toString(); output += s; logStream.write(s); });
127
128 const hardKill = setTimeout(() => {
129 try { child.kill('SIGKILL'); } catch {}
130 }, 30 * 60 * 1000); // 30 minute hard cap
131
132 child.on('error', (e: any) => {
133 clearTimeout(hardKill);
134 const msg = `spawn failed: ${e.message}`;
135 logStream.end('\n' + msg + '\n');
136 store.recordRunFinish(runId, entry.id, 'error', 127, msg, Date.now() - startMs);
137 resolve();
138 });
139

Callers 1

tickFunction · 0.70

Calls 15

getScheduleStoreFunction · 0.85
buildRecipePromptFunction · 0.85
readReceiptFileFunction · 0.85
parseReceiptFunction · 0.85
notifyDesktopFunction · 0.85
parseDeliveryTargetFunction · 0.85
formatRunSummaryFunction · 0.85
formatReceiptFunction · 0.85
deliverRunFunction · 0.85
recordRunStartMethod · 0.80
recordRunFinishMethod · 0.80
attachReceiptMethod · 0.80

Tested by

no test coverage detected