From 0b093d71dbade327436f4230051003e7c85b9bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20TEKTA=C5=9E?= Date: Tue, 18 Aug 2026 04:30:29 +0300 Subject: [PATCH 1/9] chore: add one-shot stable status patch --- .../workflows/patch-stable-status-no-now.yml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/patch-stable-status-no-now.yml diff --git a/.github/workflows/patch-stable-status-no-now.yml b/.github/workflows/patch-stable-status-no-now.yml new file mode 100644 index 0000000..0a8f7f7 --- /dev/null +++ b/.github/workflows/patch-stable-status-no-now.yml @@ -0,0 +1,48 @@ +name: Patch stable status no-now due + +on: + push: + branches: + - fix/stable-status-no-now-due + paths: + - ".github/workflows/patch-stable-status-no-now.yml" + +permissions: + contents: write + +jobs: + patch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: fix/stable-status-no-now-due + - name: Patch source and test + run: | + python - <<'PY' + from pathlib import Path + + source = Path('src/source/opencode/loop-commands.js') + text = source.read_text() + old = ' const dueIn = Number(job.runNowRequestedAt || 0) > 0 ? 0 : Math.max(0, job.intervalMs - (now() - (job.lastRunAt || 0)))\n' + new = ''' const current = now()\n const intervalMs = Number(job.intervalMs || 0)\n const lastRunAt = Number(job.lastRunAt || 0)\n const createdAt = Date.parse(job.createdAt || "")\n const dueAt = Number(job.runNowRequestedAt || 0) > 0\n ? current\n : lastRunAt > 0\n ? lastRunAt + intervalMs\n : job.immediate === false && Number.isFinite(createdAt)\n ? createdAt + intervalMs\n : current\n const dueIn = Math.max(0, dueAt - current)\n''' + if old not in text: + raise SystemExit('status due line not found') + source.write_text(text.replace(old, new, 1)) + + test = Path('scripts/loop-command-handlers-test.mjs') + text = test.read_text() + anchor = '''{\n const sessionID = "status-empty"\n''' + block = '''{\n const sessionID = "status-no-now"\n const h = harness({\n [sessionID]: {\n jobs: [loopJob("delayed", {\n intervalMs: 10_000,\n lastRunAt: 0,\n immediate: false,\n createdAt: new Date(5_000).toISOString(),\n })],\n },\n }, { now: () => 10_000 })\n await h.handlers.statusLoop("/work", {}, sessionID)\n assert.match(h.messages[0][2], /due in 5s/, "--no-now status must count the first interval from createdAt")\n}\n\n''' + if anchor not in text: + raise SystemExit('status-empty anchor not found') + test.write_text(text.replace(anchor, block + anchor, 1)) + PY + - name: Commit patch and remove helper + run: | + rm .github/workflows/patch-stable-status-no-now.yml + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add src/source/opencode/loop-commands.js scripts/loop-command-handlers-test.mjs .github/workflows/patch-stable-status-no-now.yml + git commit -m "fix: report first no-now due time correctly" + git push origin HEAD:fix/stable-status-no-now-due From d14321f96f473662cdf0d98202c4ce7765bbc7e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:30:38 +0000 Subject: [PATCH 2/9] fix: report first no-now due time correctly --- .../workflows/patch-stable-status-no-now.yml | 48 ------------------- scripts/loop-command-handlers-test.mjs | 16 +++++++ src/source/opencode/loop-commands.js | 13 ++++- 3 files changed, 28 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/patch-stable-status-no-now.yml diff --git a/.github/workflows/patch-stable-status-no-now.yml b/.github/workflows/patch-stable-status-no-now.yml deleted file mode 100644 index 0a8f7f7..0000000 --- a/.github/workflows/patch-stable-status-no-now.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Patch stable status no-now due - -on: - push: - branches: - - fix/stable-status-no-now-due - paths: - - ".github/workflows/patch-stable-status-no-now.yml" - -permissions: - contents: write - -jobs: - patch: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: fix/stable-status-no-now-due - - name: Patch source and test - run: | - python - <<'PY' - from pathlib import Path - - source = Path('src/source/opencode/loop-commands.js') - text = source.read_text() - old = ' const dueIn = Number(job.runNowRequestedAt || 0) > 0 ? 0 : Math.max(0, job.intervalMs - (now() - (job.lastRunAt || 0)))\n' - new = ''' const current = now()\n const intervalMs = Number(job.intervalMs || 0)\n const lastRunAt = Number(job.lastRunAt || 0)\n const createdAt = Date.parse(job.createdAt || "")\n const dueAt = Number(job.runNowRequestedAt || 0) > 0\n ? current\n : lastRunAt > 0\n ? lastRunAt + intervalMs\n : job.immediate === false && Number.isFinite(createdAt)\n ? createdAt + intervalMs\n : current\n const dueIn = Math.max(0, dueAt - current)\n''' - if old not in text: - raise SystemExit('status due line not found') - source.write_text(text.replace(old, new, 1)) - - test = Path('scripts/loop-command-handlers-test.mjs') - text = test.read_text() - anchor = '''{\n const sessionID = "status-empty"\n''' - block = '''{\n const sessionID = "status-no-now"\n const h = harness({\n [sessionID]: {\n jobs: [loopJob("delayed", {\n intervalMs: 10_000,\n lastRunAt: 0,\n immediate: false,\n createdAt: new Date(5_000).toISOString(),\n })],\n },\n }, { now: () => 10_000 })\n await h.handlers.statusLoop("/work", {}, sessionID)\n assert.match(h.messages[0][2], /due in 5s/, "--no-now status must count the first interval from createdAt")\n}\n\n''' - if anchor not in text: - raise SystemExit('status-empty anchor not found') - test.write_text(text.replace(anchor, block + anchor, 1)) - PY - - name: Commit patch and remove helper - run: | - rm .github/workflows/patch-stable-status-no-now.yml - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add src/source/opencode/loop-commands.js scripts/loop-command-handlers-test.mjs .github/workflows/patch-stable-status-no-now.yml - git commit -m "fix: report first no-now due time correctly" - git push origin HEAD:fix/stable-status-no-now-due diff --git a/scripts/loop-command-handlers-test.mjs b/scripts/loop-command-handlers-test.mjs index fd81901..e745618 100644 --- a/scripts/loop-command-handlers-test.mjs +++ b/scripts/loop-command-handlers-test.mjs @@ -153,6 +153,22 @@ assert.throws(() => createLoopCommandHandlers({ clearActiveRun() {} }), /cancelD assert.match(text, /goal:blocked,paused,checkpoint-only,git-checkpoint/) } +{ + const sessionID = "status-no-now" + const h = harness({ + [sessionID]: { + jobs: [loopJob("delayed", { + intervalMs: 10_000, + lastRunAt: 0, + immediate: false, + createdAt: new Date(5_000).toISOString(), + })], + }, + }, { now: () => 10_000 }) + await h.handlers.statusLoop("/work", {}, sessionID) + assert.match(h.messages[0][2], /due in 5s/, "--no-now status must count the first interval from createdAt") +} + { const sessionID = "status-empty" const h = harness({ [sessionID]: { jobs: [] } }) diff --git a/src/source/opencode/loop-commands.js b/src/source/opencode/loop-commands.js index 15d107e..04d2986 100644 --- a/src/source/opencode/loop-commands.js +++ b/src/source/opencode/loop-commands.js @@ -89,7 +89,18 @@ export function createLoopCommandHandlers(options = {}) { const state = await readState(directory, sessionID) const jobs = state.jobs || [] const lines = jobs.length ? jobs.map((job, index) => { - const dueIn = Number(job.runNowRequestedAt || 0) > 0 ? 0 : Math.max(0, job.intervalMs - (now() - (job.lastRunAt || 0))) + const current = now() + const intervalMs = Number(job.intervalMs || 0) + const lastRunAt = Number(job.lastRunAt || 0) + const createdAt = Date.parse(job.createdAt || "") + const dueAt = Number(job.runNowRequestedAt || 0) > 0 + ? current + : lastRunAt > 0 + ? lastRunAt + intervalMs + : job.immediate === false && Number.isFinite(createdAt) + ? createdAt + intervalMs + : current + const dueIn = Math.max(0, dueAt - current) const flags = [isGoalJob(job) ? `goal:${goalStatusText(job)}` : undefined, job.paused ? "paused" : "active", Number(job.runNowRequestedAt || 0) > 0 ? "run-now" : undefined, job.safe ? "safe" : undefined, job.askNever ? "ask-never" : undefined, job.noOverlap ? "no-overlap" : undefined, job.checkpointOnly ? "checkpoint-only" : undefined, job.gitCheckpoint ? "git-checkpoint" : undefined].filter(Boolean).join(",") return `${index + 1}. ${job.id}${job.name ? ` (${job.name})` : ""}: ${jobLabel(job)} | runs=${job.runCount || 0} | failures=${job.failureCount || 0} | due in ${durationToText(dueIn)} | ${flags}` }) : ["No active loop jobs."] From 4090a381cad0f02d0e3fed09b50604bed574ba45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20TEKTA=C5=9E?= Date: Tue, 18 Aug 2026 04:30:49 +0300 Subject: [PATCH 3/9] chore: trigger one-shot stable status patch --- .../workflows/patch-stable-status-no-now.yml | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/patch-stable-status-no-now.yml diff --git a/.github/workflows/patch-stable-status-no-now.yml b/.github/workflows/patch-stable-status-no-now.yml new file mode 100644 index 0000000..eaf241c --- /dev/null +++ b/.github/workflows/patch-stable-status-no-now.yml @@ -0,0 +1,49 @@ +name: Patch stable status no-now due + +# Retrigger after the workflow exists on this branch. +on: + push: + branches: + - fix/stable-status-no-now-due + paths: + - ".github/workflows/patch-stable-status-no-now.yml" + +permissions: + contents: write + +jobs: + patch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: fix/stable-status-no-now-due + - name: Patch source and test + run: | + python - <<'PY' + from pathlib import Path + + source = Path('src/source/opencode/loop-commands.js') + text = source.read_text() + old = ' const dueIn = Number(job.runNowRequestedAt || 0) > 0 ? 0 : Math.max(0, job.intervalMs - (now() - (job.lastRunAt || 0)))\n' + new = ''' const current = now()\n const intervalMs = Number(job.intervalMs || 0)\n const lastRunAt = Number(job.lastRunAt || 0)\n const createdAt = Date.parse(job.createdAt || "")\n const dueAt = Number(job.runNowRequestedAt || 0) > 0\n ? current\n : lastRunAt > 0\n ? lastRunAt + intervalMs\n : job.immediate === false && Number.isFinite(createdAt)\n ? createdAt + intervalMs\n : current\n const dueIn = Math.max(0, dueAt - current)\n''' + if old not in text: + raise SystemExit('status due line not found') + source.write_text(text.replace(old, new, 1)) + + test = Path('scripts/loop-command-handlers-test.mjs') + text = test.read_text() + anchor = '''{\n const sessionID = "status-empty"\n''' + block = '''{\n const sessionID = "status-no-now"\n const h = harness({\n [sessionID]: {\n jobs: [loopJob("delayed", {\n intervalMs: 10_000,\n lastRunAt: 0,\n immediate: false,\n createdAt: new Date(5_000).toISOString(),\n })],\n },\n }, { now: () => 10_000 })\n await h.handlers.statusLoop("/work", {}, sessionID)\n assert.match(h.messages[0][2], /due in 5s/, "--no-now status must count the first interval from createdAt")\n}\n\n''' + if anchor not in text: + raise SystemExit('status-empty anchor not found') + test.write_text(text.replace(anchor, block + anchor, 1)) + PY + - name: Commit patch and remove helper + run: | + rm .github/workflows/patch-stable-status-no-now.yml + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add src/source/opencode/loop-commands.js scripts/loop-command-handlers-test.mjs .github/workflows/patch-stable-status-no-now.yml + git commit -m "fix: report first no-now due time correctly" + git push origin HEAD:fix/stable-status-no-now-due From 2c10e9dde136ea94627370c0fab1156ecf4ca574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20TEKTA=C5=9E?= Date: Tue, 18 Aug 2026 04:31:46 +0300 Subject: [PATCH 4/9] chore: remove one-shot stable status helper --- .../workflows/patch-stable-status-no-now.yml | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/workflows/patch-stable-status-no-now.yml diff --git a/.github/workflows/patch-stable-status-no-now.yml b/.github/workflows/patch-stable-status-no-now.yml deleted file mode 100644 index eaf241c..0000000 --- a/.github/workflows/patch-stable-status-no-now.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Patch stable status no-now due - -# Retrigger after the workflow exists on this branch. -on: - push: - branches: - - fix/stable-status-no-now-due - paths: - - ".github/workflows/patch-stable-status-no-now.yml" - -permissions: - contents: write - -jobs: - patch: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: fix/stable-status-no-now-due - - name: Patch source and test - run: | - python - <<'PY' - from pathlib import Path - - source = Path('src/source/opencode/loop-commands.js') - text = source.read_text() - old = ' const dueIn = Number(job.runNowRequestedAt || 0) > 0 ? 0 : Math.max(0, job.intervalMs - (now() - (job.lastRunAt || 0)))\n' - new = ''' const current = now()\n const intervalMs = Number(job.intervalMs || 0)\n const lastRunAt = Number(job.lastRunAt || 0)\n const createdAt = Date.parse(job.createdAt || "")\n const dueAt = Number(job.runNowRequestedAt || 0) > 0\n ? current\n : lastRunAt > 0\n ? lastRunAt + intervalMs\n : job.immediate === false && Number.isFinite(createdAt)\n ? createdAt + intervalMs\n : current\n const dueIn = Math.max(0, dueAt - current)\n''' - if old not in text: - raise SystemExit('status due line not found') - source.write_text(text.replace(old, new, 1)) - - test = Path('scripts/loop-command-handlers-test.mjs') - text = test.read_text() - anchor = '''{\n const sessionID = "status-empty"\n''' - block = '''{\n const sessionID = "status-no-now"\n const h = harness({\n [sessionID]: {\n jobs: [loopJob("delayed", {\n intervalMs: 10_000,\n lastRunAt: 0,\n immediate: false,\n createdAt: new Date(5_000).toISOString(),\n })],\n },\n }, { now: () => 10_000 })\n await h.handlers.statusLoop("/work", {}, sessionID)\n assert.match(h.messages[0][2], /due in 5s/, "--no-now status must count the first interval from createdAt")\n}\n\n''' - if anchor not in text: - raise SystemExit('status-empty anchor not found') - test.write_text(text.replace(anchor, block + anchor, 1)) - PY - - name: Commit patch and remove helper - run: | - rm .github/workflows/patch-stable-status-no-now.yml - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add src/source/opencode/loop-commands.js scripts/loop-command-handlers-test.mjs .github/workflows/patch-stable-status-no-now.yml - git commit -m "fix: report first no-now due time correctly" - git push origin HEAD:fix/stable-status-no-now-due From 0981441e13df7993e9eb4e32857fe3b1e04545ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20TEKTA=C5=9E?= Date: Tue, 18 Aug 2026 04:31:57 +0300 Subject: [PATCH 5/9] chore: add one-shot stable bundle sync --- .../workflows/sync-stable-status-bundle.yml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/sync-stable-status-bundle.yml diff --git a/.github/workflows/sync-stable-status-bundle.yml b/.github/workflows/sync-stable-status-bundle.yml new file mode 100644 index 0000000..7b7e501 --- /dev/null +++ b/.github/workflows/sync-stable-status-bundle.yml @@ -0,0 +1,34 @@ +name: Sync stable status bundle + +on: + push: + branches: + - fix/stable-status-no-now-due + paths: + - ".github/workflows/sync-stable-status-bundle.yml" + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: fix/stable-status-no-now-due + - uses: actions/setup-node@v6 + with: + node-version: "24" + cache: npm + - uses: oven-sh/setup-bun@v2 + - run: npm ci + - run: npm run build:plugin + - name: Commit generated bundle and remove helper + run: | + rm .github/workflows/sync-stable-status-bundle.yml + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add src/index.js .github/workflows/sync-stable-status-bundle.yml + git commit -m "build: sync stable status bundle" + git push origin HEAD:fix/stable-status-no-now-due From 307eeabdb3ca706f7bd5e275ed02a3f685ac110a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20TEKTA=C5=9E?= Date: Tue, 18 Aug 2026 04:32:19 +0300 Subject: [PATCH 6/9] chore: trigger stable bundle sync --- .github/workflows/sync-stable-status-bundle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-stable-status-bundle.yml b/.github/workflows/sync-stable-status-bundle.yml index 7b7e501..23387e2 100644 --- a/.github/workflows/sync-stable-status-bundle.yml +++ b/.github/workflows/sync-stable-status-bundle.yml @@ -1,5 +1,6 @@ name: Sync stable status bundle +# Retrigger after the workflow exists on this branch. on: push: branches: From 2bdf57710e6a3f4044c13b26f5f7184e3645618b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:32:38 +0000 Subject: [PATCH 7/9] build: sync stable status bundle --- .../workflows/sync-stable-status-bundle.yml | 35 ------------------- src/index.js | 7 +++- 2 files changed, 6 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/sync-stable-status-bundle.yml diff --git a/.github/workflows/sync-stable-status-bundle.yml b/.github/workflows/sync-stable-status-bundle.yml deleted file mode 100644 index 23387e2..0000000 --- a/.github/workflows/sync-stable-status-bundle.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Sync stable status bundle - -# Retrigger after the workflow exists on this branch. -on: - push: - branches: - - fix/stable-status-no-now-due - paths: - - ".github/workflows/sync-stable-status-bundle.yml" - -permissions: - contents: write - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: fix/stable-status-no-now-due - - uses: actions/setup-node@v6 - with: - node-version: "24" - cache: npm - - uses: oven-sh/setup-bun@v2 - - run: npm ci - - run: npm run build:plugin - - name: Commit generated bundle and remove helper - run: | - rm .github/workflows/sync-stable-status-bundle.yml - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add src/index.js .github/workflows/sync-stable-status-bundle.yml - git commit -m "build: sync stable status bundle" - git push origin HEAD:fix/stable-status-no-now-due diff --git a/src/index.js b/src/index.js index 29f7163..a0f18eb 100644 --- a/src/index.js +++ b/src/index.js @@ -1698,7 +1698,12 @@ function createLoopCommandHandlers(options = {}) { const state = await readState2(directory, sessionID); const jobs = state.jobs || []; const lines = jobs.length ? jobs.map((job, index) => { - const dueIn = Number(job.runNowRequestedAt || 0) > 0 ? 0 : Math.max(0, job.intervalMs - (now2() - (job.lastRunAt || 0))); + const current = now2(); + const intervalMs = Number(job.intervalMs || 0); + const lastRunAt = Number(job.lastRunAt || 0); + const createdAt = Date.parse(job.createdAt || ""); + const dueAt = Number(job.runNowRequestedAt || 0) > 0 ? current : lastRunAt > 0 ? lastRunAt + intervalMs : job.immediate === false && Number.isFinite(createdAt) ? createdAt + intervalMs : current; + const dueIn = Math.max(0, dueAt - current); const flags = [isGoalJob(job) ? `goal:${goalStatusText(job)}` : undefined, job.paused ? "paused" : "active", Number(job.runNowRequestedAt || 0) > 0 ? "run-now" : undefined, job.safe ? "safe" : undefined, job.askNever ? "ask-never" : undefined, job.noOverlap ? "no-overlap" : undefined, job.checkpointOnly ? "checkpoint-only" : undefined, job.gitCheckpoint ? "git-checkpoint" : undefined].filter(Boolean).join(","); return `${index + 1}. ${job.id}${job.name ? ` (${job.name})` : ""}: ${jobLabel(job)} | runs=${job.runCount || 0} | failures=${job.failureCount || 0} | due in ${durationToText(dueIn)} | ${flags}`; }) : ["No active loop jobs."]; From 74689a7ed69c55dc39e43514ab9a1c32e64839a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20TEKTA=C5=9E?= Date: Tue, 18 Aug 2026 04:37:52 +0300 Subject: [PATCH 8/9] chore: add one-shot safe-shell test patch --- .../patch-comprehensive-safe-shell-wait.yml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/patch-comprehensive-safe-shell-wait.yml diff --git a/.github/workflows/patch-comprehensive-safe-shell-wait.yml b/.github/workflows/patch-comprehensive-safe-shell-wait.yml new file mode 100644 index 0000000..7d0793e --- /dev/null +++ b/.github/workflows/patch-comprehensive-safe-shell-wait.yml @@ -0,0 +1,39 @@ +name: Patch comprehensive safe-shell wait + +on: + push: + branches: + - fix/stable-status-no-now-due + paths: + - ".github/workflows/patch-comprehensive-safe-shell-wait.yml" + +permissions: + contents: write + +jobs: + patch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: fix/stable-status-no-now-due + - name: Replace fixed read with bounded state wait + run: | + python - <<'PY' + from pathlib import Path + p = Path('scripts/comprehensive-test.mjs') + s = p.read_text() + old = ''' await h.command("loop-now", "shell")\n const separateFlagsState = await h.readState()\n assert.equal(h.records.shells.length, 0)\n assert.equal(separateFlagsState.jobs[0].lastFailureReason, "safe_shell_blocked", "separate rm -r -f flags must be blocked")\n''' + new = ''' await h.command("loop-now", "shell")\n const separateFlagsState = await waitForValue(async () => {\n const candidate = await h.readState()\n return candidate.jobs[0]?.lastFailureReason === "safe_shell_blocked" ? candidate : undefined\n }, 2_000)\n assert.equal(h.records.shells.length, 0)\n assert.equal(separateFlagsState?.jobs[0]?.lastFailureReason, "safe_shell_blocked", "separate rm -r -f flags must be blocked")\n''' + if old not in s: + raise SystemExit('safe-shell assertion block not found') + p.write_text(s.replace(old, new, 1)) + PY + - name: Commit patch and remove helper + run: | + rm .github/workflows/patch-comprehensive-safe-shell-wait.yml + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add scripts/comprehensive-test.mjs .github/workflows/patch-comprehensive-safe-shell-wait.yml + git commit -m "test: wait for safe-shell scheduler state" + git push origin HEAD:fix/stable-status-no-now-due From 8c9b82c374e9738c70d0ebfecdc0a54e6acf0b4b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:38:00 +0000 Subject: [PATCH 9/9] test: wait for safe-shell scheduler state --- .../patch-comprehensive-safe-shell-wait.yml | 39 ------------------- scripts/comprehensive-test.mjs | 7 +++- 2 files changed, 5 insertions(+), 41 deletions(-) delete mode 100644 .github/workflows/patch-comprehensive-safe-shell-wait.yml diff --git a/.github/workflows/patch-comprehensive-safe-shell-wait.yml b/.github/workflows/patch-comprehensive-safe-shell-wait.yml deleted file mode 100644 index 7d0793e..0000000 --- a/.github/workflows/patch-comprehensive-safe-shell-wait.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Patch comprehensive safe-shell wait - -on: - push: - branches: - - fix/stable-status-no-now-due - paths: - - ".github/workflows/patch-comprehensive-safe-shell-wait.yml" - -permissions: - contents: write - -jobs: - patch: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: fix/stable-status-no-now-due - - name: Replace fixed read with bounded state wait - run: | - python - <<'PY' - from pathlib import Path - p = Path('scripts/comprehensive-test.mjs') - s = p.read_text() - old = ''' await h.command("loop-now", "shell")\n const separateFlagsState = await h.readState()\n assert.equal(h.records.shells.length, 0)\n assert.equal(separateFlagsState.jobs[0].lastFailureReason, "safe_shell_blocked", "separate rm -r -f flags must be blocked")\n''' - new = ''' await h.command("loop-now", "shell")\n const separateFlagsState = await waitForValue(async () => {\n const candidate = await h.readState()\n return candidate.jobs[0]?.lastFailureReason === "safe_shell_blocked" ? candidate : undefined\n }, 2_000)\n assert.equal(h.records.shells.length, 0)\n assert.equal(separateFlagsState?.jobs[0]?.lastFailureReason, "safe_shell_blocked", "separate rm -r -f flags must be blocked")\n''' - if old not in s: - raise SystemExit('safe-shell assertion block not found') - p.write_text(s.replace(old, new, 1)) - PY - - name: Commit patch and remove helper - run: | - rm .github/workflows/patch-comprehensive-safe-shell-wait.yml - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add scripts/comprehensive-test.mjs .github/workflows/patch-comprehensive-safe-shell-wait.yml - git commit -m "test: wait for safe-shell scheduler state" - git push origin HEAD:fix/stable-status-no-now-due diff --git a/scripts/comprehensive-test.mjs b/scripts/comprehensive-test.mjs index 629b8f3..1b08697 100644 --- a/scripts/comprehensive-test.mjs +++ b/scripts/comprehensive-test.mjs @@ -358,9 +358,12 @@ async function testActionRoutingAndSafety() { await h.command("loop-clear") await h.command("loop-shell", "0s --safe rm -r -f ./important") await h.command("loop-now", "shell") - const separateFlagsState = await h.readState() + const separateFlagsState = await waitForValue(async () => { + const candidate = await h.readState() + return candidate.jobs[0]?.lastFailureReason === "safe_shell_blocked" ? candidate : undefined + }, 2_000) assert.equal(h.records.shells.length, 0) - assert.equal(separateFlagsState.jobs[0].lastFailureReason, "safe_shell_blocked", "separate rm -r -f flags must be blocked") + assert.equal(separateFlagsState?.jobs[0]?.lastFailureReason, "safe_shell_blocked", "separate rm -r -f flags must be blocked") } finally { await h.cleanup() }