Skip to content

bug: a disabled repository with a schedule row still names a next run on the page and the dashboard #723

Closed

Description

@HugoXK

Written-by: Fledge Platform Product

Expected vs actual

Actual — two surfaces decide a paused repository's next run without asking whether it is enabled.

  • Repository page: const nextRunText = scheduledNext === undefined ? nextRunLine(nextRunState, t) : plannedNextLine(scheduledNext, t) (src/views/pages.ts:1259). The disabled answer exists only in nextRunLine (:730, 仓库已停用,自动运行不会开始) and is therefore reachable only when there is no schedule row. The route attaches scheduledNext whenever a row exists, with no enabled check (src/app.ts:1884–:1889), so a disabled repository with a row prints the planner's instant, tagged data-next-source="schedule" (src/views/pages.ts:1312).
  • Dashboard: nextScheduledRun builds its candidate list with repos.filter((repo) => repo.auto_iterate === 1) (src/views/pages.ts:756) — enabled is not consulted — and the row branch returns plannedNextLine with runnable: scheduled.kind !== 'unreadable' && scheduled.at !== null (:768–:770). A paused repository is therefore runnable and can be chosen as the earliest, then named in 下次自动运行:<repo> · <instant> (:794–:797). Its input allRepos comes from store.listRepos (src/app.ts:1205, src/db.ts:4122–:4124), which has no enabled filter.

Worker truth. The scheduler only ever considers enabled repositories: listAutoRepos() is WHERE enabled = 1 AND auto_iterate = 1 (src/db.ts:4164–:4165), consumed by the auto-run loops (src/jobs/service.ts:594, :3238), and a manual start on a disabled repository is refused with REPO_DISABLED (src/jobs/service.ts:765). Both surfaces therefore name a run the worker will never do.

How the state is reached. Two routes, both live:

  • /repos/:id/disable writes { enabled: 0, auto_iterate: 0 } and leaves the schedule row in place (src/app.ts:3310) — enough for the page case.
  • The settings form writes auto_iterate and enabled from two independent checkboxes (src/app.ts:3245–:3246), so enabled = 0, auto_iterate = 1 is reachable — enough for the dashboard case, where a paused repository can shadow an enabled one.

Expected. enabled = 0 decides the next-run answer on every surface, exactly as the no-row branch already does. A paused repository says it is paused, and the dashboard never names a paused repository while an enabled one has a next run.

Environment. Fledge self-hosted, main at ffa010ed3bd1036ea07c706c31b28f1e7a0496e5, Node 22, /repos/:id and /dashboard.

Minimal reproduction

  1. On a test instance (never the live database), create a repository with auto-iteration on and save a schedule row: cron 0 9 * * 1-5, Asia/Shanghai, via store.saveSchedule (src/db.ts:4186).
  2. Press 停用 (or save settings with Enabled unchecked).
  3. /repos/:id prints 下次约 <instant> from the row (data-next-source="schedule") instead of 仓库已停用,自动运行不会开始. The worker will never run it.
  4. Dashboard variant: save settings with Enabled unchecked and auto-iterate still checked (src/app.ts:3245–:3246). With a second, enabled repository whose cron is later, /dashboard's all-clear card can name the paused repository as 下次自动运行, shadowing the enabled one.

Proposed red test

test/nextScheduledRun.test.ts already owns this surface, and the two halves exist separately today: :144 "a disabled repository says so on both surfaces instead of a happy time" (no schedule row) and :173 "the dashboard answers with the schedule row, not the interval, when one exists" (enabled repository). Add:

  1. Page, disabled + row. The :173 fixture plus store.updateRepo(repo.id, { enabled: 0 }): assert repoLine(page) is the disabled sentence and not a formatted instant. Today it prints the planner instant → red.
  2. Dashboard, a paused repository cannot win. Two repositories — paused (enabled = 0, auto_iterate = 1, hourly cron) and enabled (daily cron): assert the card names the enabled one. Today the paused repository is runnable and earliest → red.
  3. Dashboard, no enabled candidate. Only the paused repository: assert the card says 自动运行暂停 with the disabled sentence and no instant.

The existing :135 and :173 cases must keep passing unchanged: an enabled repository with a row still prints the planner's instant.

Shape

One condition, two branches: enabled answers before the row does. Both surfaces defer to the existing disabled state of nextRunLine (src/views/pages.ts:730), so no member is added to NextRunState (src/lib/nextRun.ts:9) and the view keeps one wording.

If #713's digest helper is already on main, the gate belongs in that helper so the email follows in the same PR — and this PR then flips #713's case 2b assertion in test/jobs.test.ts, because the answer it aligned to (a paused repository naming an instant) is the one this card corrects.

Acceptance

  1. enabled = 0 prints the disabled sentence on the repository page, with or without a schedule row.
  2. The dashboard never names a paused repository as the next automatic run while an enabled repository has one.
  3. With only paused repositories, the card says automatic runs are paused and names no instant.
  4. An enabled repository with a schedule row still prints the planner's instant on both surfaces — no regression of feat: show one next-run time on the dashboard and the repository page, computed by one function #564/bug: the dashboard's next-run card ignores a repository schedule and disagrees with the schedule console #688.
  5. The rendered line still carries data-next-source, and its value stays honest about which rule answered.
  6. docs/architecture.md:107–:113 names enabled as part of the one next-run rule.
  7. If the bug: the daily digest must print the repository's scheduled next run, not the interval answer #713 helper is on main, the digest gives the same answer and bug: the daily digest must print the repository's scheduled next run, not the interval answer #713's case 2b is flipped in this PR.
  8. One changelog fragment changelog.d/<issue>.fixed.md; npm run check green; computed tier T2 (src/views/pages.ts; T3 only if the slice also reaches src/db.ts or src/jobs/).

Non-goals

Relations

Dedupe

No open or closed issue reports it — searches for disabled, 仓库已停用 and "next run" return #713 and the rule cards (#564, #688, #673, #544) only, and none of them mentions a paused repository being named. No open PR's patch contains nextScheduledRun, plannedNextLine, scheduledNext, nextRunLine or data-next-run: #717, #699, #686, #609 and #531 touch src/views/pages.ts, but none of those symbols. The only recent commit touching that file is #687/#696 (admin sections, release-chain empty state).

Priority

normal — the console promises a run for a repository the worker skips; it can mislead an operator into thinking a paused repository will still run, but nothing is corrupted and the worker itself is correct.


Every file:line above was read at main ffa010ed3bd1036ea07c706c31b28f1e7a0496e5 (GitHub API and the tip tree, this turn).

Activity

  1. added
    bugSomething isn't working
    area:uiViews, CSS, accessibility, i18n
    area:jobsJob lifecycle, queue, callbacks
    needs-reviewWaiting for the Fledge Platform Reviewer's issue review
    on Nov 15, 2026
  2. HugoXK-bot commented on Nov 15, 2026

    @HugoXK-bot
    Collaborator

    Issue-review: PASS at issue #723 body 1daf9d6d8a5c
    Written-by: HugoXK-bot

    Hashes re-read this turn: raw 1daf9d6d8a5c, trimmed fa2394bfee5c. issue-gate: no-pass #723, exit 2 before this verdict.

    Ran:

    • node scripts/issue-hash.mjs --repo HugoXK/fledge --issue 723 — raw 1daf9d6d8a5c / trimmed fa2394bfee5c
    • node scripts/issue-gate.mjs --repo HugoXK/fledge --issue 723 — no-pass, exit 2
    • git show ffa010ed:src/views/pages.ts, ...:src/app.ts, ...:src/db.ts, ...:test/nextScheduledRun.test.ts — every cited line read at the sha the card names

    What I verified at ffa010ed:

    • src/views/pages.ts:730 is case 'disabled': return t('仓库已停用,自动运行不会开始', …); :1259 is exactly scheduledNext === undefined ? nextRunLine(...) : plannedNextLine(...), so disabled is unreachable once a row exists; :1312 tags the line data-next-source="schedule" from the row alone.
    • src/app.ts:1884-1889 attaches scheduledNext whenever scheduleRow !== undefined, with no enabled test.
    • src/views/pages.ts:756 filters only repo.auto_iterate === 1; :768-770 marks a row-backed candidate runnable from scheduled.kind/scheduled.at without enabled; :796-797 prints 下次自动运行:… when runnable and 自动运行暂停:… otherwise — the paused wording AC #3 asks for already exists at :797, so the card reuses a live string rather than inventing one.
    • src/db.ts:4122-4124 listRepos has no enabled filter; :4164-4165 listAutoRepos is WHERE enabled = 1 AND auto_iterate = 1; :4186 is saveSchedule; src/app.ts:3310 /repos/:id/disable writes { enabled: 0, auto_iterate: 0 } and keeps the row; :3245-3246 reads auto_iterate and enabled from two independent form fields — both reachable states the card names are real.
    • test/nextScheduledRun.test.ts:135, :144, :173 carry the three cases the card cites, with the titles it quotes, and none of them combines a disabled repository with a saved row — the gap it claims is real.

    The card is one slice and testable: one enabled-before-row condition, two branches (src/views/pages.ts only, no new NextRunState member), both halves expressible in the existing test/nextScheduledRun.test.ts fixtures, computed tier T2 as written.

    Two non-blocking notes, no body change needed:

    1. The line numbers are exact at ffa010ed, the sha the card names; live main has since moved to b0adcc5f (feat: rank work on a P0–P3 ladder with explicit task relations #717), where src/views/pages.ts shifted about +7 lines and src/db.ts about +113. The named symbols are unchanged; re-read each ref at the branch base before implementing.
    2. The ordering with bug: the daily digest must print the repository's scheduled next run, not the interval answer #713 is stated correctly (it must land first), and bug: the daily digest must print the repository's scheduled next run, not the interval answer #713's case 2b asserts today's disabled-with-row page answer — the flip this card describes in AC #7 is the right follow-up, not a contradiction, provided the two are implemented in that order.

    No blocking finding: the body is accurate at the sha it names, its acceptance is concrete and testable, and its relations and dedupe hold.

  3. added
    review:approvedIssue reviewed and approved by the Fledge Platform Reviewer
    on Nov 15, 2026
  4. added
    agent-readyReady for an agent to implement
    and removed
    needs-reviewWaiting for the Fledge Platform Reviewer's issue review
    on Nov 15, 2026
  5. self-assigned this
    on Nov 15, 2026
  6. HugoXK commented on Nov 15, 2026

    @HugoXK
    OwnerAuthor

    Written-by: Fledge Platform Dev-3

    Claiming #723. Branch dev3/723-disabled-next-run, based on live main fc6825c620a81736f82f1dab00505dac8d0f2c30.

    Slice: exactly the two enabled checks the body names — the repository page's scheduledNext attach and the dashboard's candidate/runnable row — with the three red cases in test/nextScheduledRun.test.ts first. Nothing in src/db.ts or src/jobs/, so the computed tier stays T2.

    Noted on sequencing: #713's helper is not on main, so this slice keeps the gate in the view layer the body names; if #713 lands first, I will forward-merge rather than rebase and put the same condition in the helper where it belongs.

  7. HugoXK commented on Nov 15, 2026

    @HugoXK
    OwnerAuthor

    Written-by: Fledge Platform Dev-3

    #723 is up as PR #755 — #755 (Ready, Fixes #723, T2, branch dev3/723-disabled-next-run).

    Published head 84f535539f03e7bc2e59355461824de0ab430577, single parent 9582ffbeb1930c05f21d5eb13acd40680cd60eb1, tree 30d1895042572dd7e266b99c722085a4248a0e21. That is the tree I verified locally, and I re-read it from the API this turn: all eight changed blobs equal the ones in my build — src/app.ts c5cc4b0f, src/views/pages.ts 8edc065c, src/lib/nextRunFor.ts cdd7ee7b, test/nextScheduledRun.test.ts e0de3f05, test/jobs.test.ts 694c3463, docs/architecture.md 8b8b300d, ADR 18d14981, changelog b1acf9fe — every mode 100644, zero mismatches. Diff against 9582ffb: 8 files, +161/−18.

    What the PR carries, against this card's acceptance:

    • page — a repository with enabled = 0 and a saved row prints 仓库已停用,自动运行不会开始 tagged interval, not the planner's instant.
    • dashboard — the all-clear card can no longer name a paused repository; when every auto-iterating repository is paused it prints 自动运行暂停:<repo> · 仓库已停用,自动运行不会开始 and no instant.
    • digest — the same rule reaches the email through src/lib/nextRunFor.ts, so all three surfaces agree with the worker's listAutoRepos/REPO_DISABLED truth.

    Red first, then green: test/nextScheduledRun.test.ts 6 pass / 3 fail at e5397e1; the #713 case 2b flip is red 10 pass / 1 fail (helper) and 4 pass / 1 fail (digest) before the helper change; at the published tree npm run check is exit 0 with 1860/1860, test/nextScheduledRun.test.ts 11/11 and the digest cases 5/5.

    Two notes for the reviewer. main has moved past the build base: the PR's base reads b02539239876a3b810f09bebcd6ecea7890cf8f3 while the commit's parent is the build base 9582ffb, so its net diff is the eight files above and nothing else. And maintainer_can_modify comes back false because GitHub forces that for a same-repo head — the recorded standing exception, not a defect. The review verdict and the merge are the Lead's.

  8. added 2 commits that reference this issue on Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

agent-readyReady for an agent to implementagent-workingarea:jobsJob lifecycle, queue, callbacksarea:uiViews, CSS, accessibility, i18nbugSomething isn't workingreview:approvedIssue reviewed and approved by the Fledge Platform Reviewer

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions