Scheduled: where a task runs (runs_on), and choosing a linked computer in the form
ci / ui (push) Waiting to run
ci / check (push) Failing after 4s
ci / start (push) Failing after 4s

The task page says the computer, folder, mode and approval wait, and whether
the computer is away or does not accept scheduled work (/devices
`scheduled`); a run waiting for it says so. The form offers Where it runs
where the server's devices say whether they accept, sending runs_on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 14:24:24 +00:00
1 parent 3101552858
commit df27ca82fa
9 files changed
+501 -8

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
{
"schedule": {
"id": "sched3",
"title": "Nightly tests",
"instruction": "Run the tests.",
"request": "every night at 02:00",
"rule": {
"every": {
"minutes": 1440
}
},
"description": "Every 1 day",
"timezone": "UTC",
"enabled": true,
"next_run": "2026-01-03T02:00:00+00:00",
"next_runs": [],
"target": "chat",
"mode": "fresh",
"notify": "matters",
"catch_up": "one",
"grace_minutes": null,
"model": null,
"effort": null,
"agents": null,
"tools": [
"files",
"recall",
"search",
"fetch",
"memory",
"skills",
"plan"
],
"origin": "user",
"chat_id": "chat2",
"fired_count": 3,
"failure_streak": 0,
"watch": {
"kind": "command",
"command": "git log -1 --format=%H",
"timeout_s": 30,
"target": "git log -1 --format=%H",
"changed_at": "2026-01-02T03:04:05+00:00"
},
"runs_on": {
"device": "tok1",
"device_name": "bag-end",
"cwd": "/home/frodo/waybread",
"mode": "edit",
"approval_wait_minutes": 30
},
"last_error": null,
"last_run": {
"id": "run5",
"schedule_id": "sched3",
"status": "waiting",
"timing": "on_time",
"missed": 0,
"due_at": "2026-01-02T03:04:05+00:00",
"started_at": null,
"finished_at": null,
"waiting_for": "device",
"retry_at": "2026-01-02T03:09:05+00:00",
"progress": {
"phase": "retrying",
"since": 1767323045000,
"reason": "Waiting for bag-end to be linked.",
"in": 0,
"attempt": 1
},
"attempts": 1,
"model": {
"id": null,
"name": null,
"reason": null,
"switched_from": null
},
"tokens": {
"prompt": 0,
"completion": 0
},
"cost": null,
"chat_id": null,
"message_id": null,
"report_path": null,
"preview": "",
"error": {
"class": "device_away",
"text": "Waiting for bag-end to be linked."
},
"notified": false,
"notification": null,
"watch": null
},
"running": false,
"created_at": "2026-01-02T03:04:05+00:00"
}
}
+96
View File
@@ -1586,6 +1586,102 @@ test("K8: nothing is offered that is not newer, a beta to a stable install, or a
}
})
// L8: where a scheduled task runs (runs_on, fixture schedule_on_device.json; /devices `scheduled`).
function l8(devices) {
const on = fixture("schedule_on_device")
const id = on.schedule.id
return {
status: status({ host: "workbench" }),
routes: baseRoutes({
"GET /devices": { devices },
"GET /schedules": { schedules: [on.schedule] },
[`GET /schedules/${id}`]: on,
[`GET /schedules/${id}/runs`]: { runs: [on.schedule.last_run], more: false },
"GET /schedules/models": { models: [], instance: [], mine: [] },
"POST /schedules/compile": { ok: true, reason: "", title: "Build check", instruction: "Check the build.", target: "chat", rule: { at: { times: ["07:30"] } }, description: "Every day at 07:30", next_runs: [], timezone: "UTC" },
"POST /schedules": { schedule: { ...on.schedule, id: "sched9" } },
"GET /schedules/sched9": { schedule: { ...on.schedule, id: "sched9" } },
"GET /schedules/sched9/runs": { runs: [], more: false },
}),
streams: {},
}
}
const bagEnd = (over = {}) => ({ id: "tok1", name: "bag-end", host: "bag-end", platform: "linux", terminal: true, scheduled: true, max_mode: "", ...over })
async function openTask(p) {
await p.waitForSelector("#composer")
await p.evaluate(() => window.__mock.emit("open-schedule", { schedule: "sched3", run: "run5" }))
await p.waitForSelector(".fact >> text=Where it runs")
}
test("L8: a task on a linked computer says the computer, the folder and mode; a run waits for it", async () => {
const p = await page(l8([bagEnd()]))
await openTask(p)
const where = await p.textContent(".fact:has-text('Where it runs')")
assert.match(where, /bag-end/)
assert.match(where, /\/home\/frodo\/waybread/)
assert.match(where, /Edit — asks before commands · approvals wait 30 min/)
assert.equal(await p.locator(".fact.is-warning").count(), 0)
assert.match(await p.textContent(".run[data-id='run5']"), /Waiting for the computer/)
assert.match(await p.textContent(".run[data-id='run5']"), /Waiting for bag-end to be linked\./)
await shot(p, "l8-task-1280")
assert.deepEqual(p.errors, [])
})
test("L8: a computer away, or not accepting scheduled work, is said on the task", async () => {
for (const [devices, words] of [
[[], /Away now/],
[[bagEnd({ scheduled: false })], /does not accept scheduled work/],
]) {
const p = await page(l8(devices))
await openTask(p)
assert.match(await p.textContent(".fact.is-warning"), words)
assert.deepEqual(p.errors, [])
await p.context().close()
}
})
test("L8: a new task can run on this computer, in a folder and mode; one not accepting cannot be chosen", async () => {
const p = await page(l8([bagEnd({ scheduled: false }), bagEnd({ id: "token1", name: "workbench", host: "workbench" })]))
await p.waitForSelector("#composer")
await p.evaluate(() => window.__mock.emit("open-schedule", { schedule: "sched3", run: "run5" }))
await p.waitForSelector("#page-scheduled:not([hidden])")
await p.keyboard.press("Control+k")
await p.fill(".palette__input", "New scheduled")
await p.keyboard.press("Enter")
await p.fill("#sched-words", "every day at 7:30 check the build")
await p.click(".sched--new button[type=submit]")
await p.waitForSelector("#sched-where")
const options = await p.$$eval("#sched-where option", (l) => l.map((o) => [o.textContent, o.disabled]))
assert.deepEqual(options, [["On this server", false], ["bag-end — does not accept scheduled work", true], ["This computer (workbench)", false]])
await p.selectOption("#sched-where", "token1")
assert.equal(await p.inputValue("#sched-cwd"), "/home/dev/project")
await p.selectOption("#sched-runmode", "auto")
await p.fill("#sched-wait", "45")
await shot(p, "l8-form-1280")
await p.click("#sched-save")
await p.waitForFunction(() => window.__mock.api("POST", "/schedules").some((c) => c.args.path === "/schedules"))
const body = (await p.evaluate(() => window.__mock.api("POST", "/schedules").find((c) => c.args.path === "/schedules"))).args.body
assert.deepEqual(body.runs_on, { device: "token1", cwd: "/home/dev/project", mode: "auto", approval_wait_minutes: 45 })
assert.deepEqual(p.errors, [])
})
test("L8: at 390 px the form with a computer chosen stays inside the window", async () => {
const p = await page(l8([bagEnd()]), { width: 390, height: 844 })
await p.waitForSelector("#composer")
await p.evaluate(() => window.__mock.emit("open-schedule", { schedule: "sched3", run: "run5" }))
await p.waitForSelector(".fact >> text=Where it runs")
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
await shot(p, "l8-task-390")
await p.click("#sched-edit")
await p.waitForSelector("#sched-cwd")
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
await shot(p, "l8-form-390")
assert.deepEqual(p.errors, [])
})
// --- Run ----------------------------------------------------------------------------------------
const only = process.argv[2] || ""