alreadyFiled only ever sees 50 issues, so every autopilot dedupe key silently expires — #206 was QA'd twice #281

Closed
opened 2026-09-03 00:57:04 +00:00 by viberfox-agent · 13 comments
Collaborator

Problem

alreadyFiled fetches the issue list once, unpaged, and Forgejo caps a page at 50 no matter what limit asks for:

const all = await api(`/issues?state=all&type=issues&limit=100`);
issueBodies = all.map((i: any) => `${i.body ?? ""}`);

tools/autopilot.ts:606-612. The api helper is a bare single fetch with no paging of its own (tools/autopilot.ts:139-146).

Measured against this server today (read-only GETs, no build):

GET /repos/jeroen/cartopolis/issues?state=all&type=issues&limit=100  → 50 issues, #284 … #231
GET /repos/jeroen/cartopolis/issues?state=all&type=issues&limit=50   → 50 issues, #284 … #231
GET …&limit=50&page=1..3                                             → 103 issues, #284 … #1

So every autopilot:<key> marker older than the 50 most recent issues is invisible, and nothing logs it — from the function's point of view the key genuinely is not there. All five filing paths depend on it: fileRedMainTicket (tools/autopilot.ts:541), the frontier loop and its refill (tools/autopilot.ts:682, :736), the QA re-verify and the dated QA rotation (tools/autopilot.ts:900, :917), and the retro period key (tools/autopilot.ts:980).

Two duplicates already exist on the board, not one. I scanned every autopilot:<key> marker across all 103 issues; exactly two keys appear twice:

  • qa-reverify-206 — #210 (2026-08-30) and #266 (2026-09-02), the case this ticket was filed for.
  • log — #181 and #257 are both open, both titled "Autopilot log", both carrying <!-- autopilot:log -->. logIssue looks the marker up through the same unpaged fetch (tools/autopilot.ts:1061-1063), did not find #181, and made a second one. #181's daily notes stop 2026-09-01; #257's start 2026-09-02. The lane's own "silence is readable" instrument split its history in half and said nothing.

Three more call sites are one page away from the same failure and are not currently wrong only by luck:

  • tools/autopilot.ts:646 — the open-ticket gate that enforces maxOpen. Truncation here undercounts in-flight tickets, so the lane files past its own quota. 47 open issues today, three short of the cap.
  • tools/autopilot.ts:214 (rearmParkedTickets) and tools/autopilot.ts:890 (the QA open gate) read the same unpaged open list.
  • tools/autopilot.ts:1107 (digest) counts what was filed, closed and parked from the truncated list.

The idiom to copy is already in the file — sweepMergedBranches pages /branches correctly at tools/autopilot.ts:936-942.

The q= route the ticket suggests is already rejected in the code, and the reason still holds: the comment at tools/autopilot.ts:600-604 records that Forgejo's q= is a fuzzy match over title and body, that autopilot:pdok-aerial-imagery returns unrelated issues under it, and that a false positive makes the frontier look fully filed so nothing is ever proposed again. Page it; do not switch to search.

Approach

One file: tools/autopilot.ts. No Rust, no crate touched.

  1. Add a paging helper beside api (tools/autopilot.ts:139) — pagedApi(path, token?) that appends page=n&limit=50, concatenates, and stops on a short page, the same shape as tools/autopilot.ts:938-942. Give it a page ceiling so a server that never returns a short page cannot loop for ever.
  2. Memoise the full issue list once per process — allIssues() returning the raw objects, filled by one paged walk (3 requests today). alreadyFiled derives its bodies from it (replacing the issueBodies cache at tools/autopilot.ts:604), and logIssue (:1062) and digest (:1107) read the same memo instead of each doing their own truncated fetch. This is the same intra-tick staleness the process already has — issueBodies is cached for the life of the process today, and every filing path uses a distinct key — so nothing changes there.
  3. Page the three open-issue lists at tools/autopilot.ts:214, :646 and :890.
  4. Keep the local substring scan and the state=all scope exactly as they are, and keep the comment at tools/autopilot.ts:600-604 (extend it to say the list is paged, and why q= is still not the answer).
  5. Make the file testable. It currently has no exports and a bare top-level dispatcher (tools/autopilot.ts:1193-1221) that reads process.argv and throws without FORGEJO_TOKEN, so importing it runs the autopilot. Wrap that block in if (import.meta.main) and export the functions under test. This is a mechanical change to the entrypoint; the four command paths must behave identically when run as bun tools/autopilot.ts <cmd>.
  6. Add tools/autopilot.test.ts — stub globalThis.fetch with a fake issue list of >50 entries carrying a marker on an old one, and assert alreadyFiled finds it, that the walk stops on a short page, and that it issues more than one request.

Leave shippedRecently (tools/autopilot.ts:821) at limit=30: it is label-filtered and cut to 7 days, and 16 such issues exist. Note it in the paging comment as a deliberate window, not an oversight.

Acceptance criteria

  • alreadyFiled walks every page of /issues?state=all&type=issues and finds a marker on an issue more than 50 issues old.
  • Given today's board (103 issues), alreadyFiled("qa-reverify-206") returns true and alreadyFiled("log") returns true.
  • The full issue list is fetched once per process, not once per consumer: alreadyFiled, logIssue and digest share it.
  • logIssue finds an existing "Autopilot log" issue regardless of its age, so no third one is created.
  • The open-issue reads at tools/autopilot.ts:214, :646 and :890 are paged, so the maxOpen gate counts every open autopilot ticket once the board passes 50 open issues.
  • The paging loop terminates on a short page and has an explicit page ceiling; it does not loop for ever against a server that always returns a full page.
  • Marker matching is still a local substring scan over bodies; no q= search is introduced, and the reason recorded at tools/autopilot.ts:600-604 is preserved.
  • tools/autopilot.test.ts exists, stubs fetch, and fails if the fetch is reverted to a single unpaged request.
  • Importing tools/autopilot.ts runs no commands and requires no environment variables; bun tools/autopilot.ts status, ship, generate and tick still dispatch as before.
  • The commit body says what the cap is, that it had already fired twice (#210/#266 and #181/#257), and why q= is not the fix.

Verification

bun test tools/autopilot.test.ts
bun tools/autopilot.ts status          # must still print config/state and exit 0

Live read-only check, with FORGEJO_TOKEN sourced from ~/.config/cartopolis/forgejo.env (never status-safe to print — it echoes config, not the token):

# the cap itself: both of these return 50
curl -s -H "Authorization: token $FORGEJO_TOKEN" \
  "$FORGEJO_API/repos/$FORGEJO_REPO/issues?state=all&type=issues&limit=100" | jq length
# the paged walk: 103 today, #284 down to #1
for p in 1 2 3; do curl -s -H "Authorization: token $FORGEJO_TOKEN" \
  "$FORGEJO_API/repos/$FORGEJO_REPO/issues?state=all&type=issues&limit=50&page=$p" | jq length; done

A dry end-to-end check on the real board is bun tools/autopilot.ts status plus reading the log line from generate — but generate files tickets, so do not run it against production to test this. Nothing here needs a GPU, a workstation, --shot or cargo shots.

Out of scope

  • Merging or closing the duplicate log issue. #181 and #257 both exist; after this fix logIssue keeps using whichever is newest-first (#257) and makes no more. Reconciling the two is a maintainer's call on the board, not a code change, and this branch should not touch either issue.
  • Closing #266 or #210. The duplicate QA pass already happened and #266 produced #268.
  • A CI lane for the test. There is no bun step anywhere in .forgejo/workflows/ and no package.json in the repo, so gating this would mean adding setup-bun or a runner-image change — a host concern, larger than the bug. The test is run by hand with the command above.
  • #239's separate bug — the frontier refill key always being frontier-refill-0. Same key family, different defect; fixing the paging does not fix it and this branch should not fold it in.
  • shippedRecently's limit=30 (tools/autopilot.ts:821), which is a deliberate 7-day window over a label-filtered list.
  • Any change to what the markers are or where they live. Bodies stay the store, state=all stays the scope.
  • A general retry/backoff or rate-limit policy for api.

Open questions

None.


Branch: fix/281-autopilot-page-issue-lists

Original request

Found by the 7-day retrospective on #279, while working out why one ticket was QA'd
twice.

What happens

alreadyFiled is the autopilot's only guard against filing the same ticket twice.
Every filing path goes through it: the red-main ticket, the frontier refills, the
QA re-verifies and the QA rotation.

async function alreadyFiled(key: string): Promise<boolean> {
  if (!issueBodies) {
    const all = await api(`/issues?state=all&type=issues&limit=100`);
    issueBodies = all.map((i: any) => `${i.body ?? ""}`);
  }
  return issueBodies.some((b) => b.includes(`autopilot:${key}`));
}

tools/autopilot.ts:606.

Forgejo caps a page at 50 regardless of what limit asks for. Measured against
this server today:

GET /repos/jeroen/cartopolis/issues?state=all&type=issues&limit=100
  → 50 issues, #279 down to #222

So alreadyFiled answers "no" for every key older than the 50 most recent issues,
and there is no second page. Every dedupe key in the system silently expires after
50 issues. Nothing logs it, because from the function's point of view the key
genuinely is not there.

The instance

#206 was QA'd twice. Both tickets carry the same marker:

  • #210 — QA: what #206 shipped — filed 2026-08-30, <!-- autopilot:qa-reverify-206 -->
  • #266 — QA: what #206 shipped — filed 2026-09-02, <!-- autopilot:qa-reverify-206 -->

56 issues apart, which is past the window. The second pass cost a full session and
re-walked ground the first had already covered. (It was not wasted — it filed #268
— but nothing chose to spend a session that way.)

The same hole is open under the others:

  • main is red on <sha> can be filed twice for one commit once 50 issues have
    gone by, and its comment path says "a ticket for it exists — waiting", so a
    duplicate does not just cost a ticket, it un-parks a stop.
  • frontier-refill-N and the dated qa-<target>-<date> keys expire the same way.
  • #239 records a different bug in the same key family — the refill key is always
    frontier-refill-0. Between the two, the frontier's memory is doubly unreliable.

What would fix it

Page it, the way any other list in this file would have to be. limit=50 and
page=n until a short page comes back, or read only what is needed: the marker is
a fixed string, so Forgejo's issue search (q=) answers the same question in one
request without holding 50 bodies in memory.

A test is available and cheap: assert that a key present on an issue more than 50
issues old is still found.

Where the seam is

tools/autopilot.ts:606 — alreadyFiled. The issueBodies cache above it is fine;
it is the single unpaged fetch that fills it.

Filed by the 7-day retrospective (#279).

🤖 Refined by the viberfox issue agent. Reply with @agent refine and what is wrong to have this rewritten.

## Problem `alreadyFiled` fetches the issue list once, unpaged, and Forgejo caps a page at 50 no matter what `limit` asks for: ```ts const all = await api(`/issues?state=all&type=issues&limit=100`); issueBodies = all.map((i: any) => `${i.body ?? ""}`); ``` `tools/autopilot.ts:606-612`. The `api` helper is a bare single `fetch` with no paging of its own (`tools/autopilot.ts:139-146`). Measured against this server today (read-only GETs, no build): ``` GET /repos/jeroen/cartopolis/issues?state=all&type=issues&limit=100 → 50 issues, #284 … #231 GET /repos/jeroen/cartopolis/issues?state=all&type=issues&limit=50 → 50 issues, #284 … #231 GET …&limit=50&page=1..3 → 103 issues, #284 … #1 ``` So every `autopilot:<key>` marker older than the 50 most recent issues is invisible, and nothing logs it — from the function's point of view the key genuinely is not there. All five filing paths depend on it: `fileRedMainTicket` (`tools/autopilot.ts:541`), the frontier loop and its refill (`tools/autopilot.ts:682`, `:736`), the QA re-verify and the dated QA rotation (`tools/autopilot.ts:900`, `:917`), and the retro period key (`tools/autopilot.ts:980`). **Two duplicates already exist on the board**, not one. I scanned every `autopilot:<key>` marker across all 103 issues; exactly two keys appear twice: - `qa-reverify-206` — #210 (2026-08-30) and #266 (2026-09-02), the case this ticket was filed for. - `log` — **#181 and #257 are both open, both titled "Autopilot log"**, both carrying `<!-- autopilot:log -->`. `logIssue` looks the marker up through the same unpaged fetch (`tools/autopilot.ts:1061-1063`), did not find #181, and made a second one. #181's daily notes stop 2026-09-01; #257's start 2026-09-02. The lane's own "silence is readable" instrument split its history in half and said nothing. Three more call sites are one page away from the same failure and are not currently wrong only by luck: - `tools/autopilot.ts:646` — the open-ticket gate that enforces `maxOpen`. Truncation here undercounts in-flight tickets, so the lane files past its own quota. **47 open issues today**, three short of the cap. - `tools/autopilot.ts:214` (`rearmParkedTickets`) and `tools/autopilot.ts:890` (the QA open gate) read the same unpaged open list. - `tools/autopilot.ts:1107` (`digest`) counts what was filed, closed and parked from the truncated list. The idiom to copy is already in the file — `sweepMergedBranches` pages `/branches` correctly at `tools/autopilot.ts:936-942`. **The `q=` route the ticket suggests is already rejected in the code**, and the reason still holds: the comment at `tools/autopilot.ts:600-604` records that Forgejo's `q=` is a fuzzy match over title and body, that `autopilot:pdok-aerial-imagery` returns unrelated issues under it, and that a false positive makes the frontier look fully filed so nothing is ever proposed again. Page it; do not switch to search. ## Approach One file: `tools/autopilot.ts`. No Rust, no crate touched. 1. **Add a paging helper** beside `api` (`tools/autopilot.ts:139`) — `pagedApi(path, token?)` that appends `page=n&limit=50`, concatenates, and stops on a short page, the same shape as `tools/autopilot.ts:938-942`. Give it a page ceiling so a server that never returns a short page cannot loop for ever. 2. **Memoise the full issue list once per process** — `allIssues()` returning the raw objects, filled by one paged walk (3 requests today). `alreadyFiled` derives its bodies from it (replacing the `issueBodies` cache at `tools/autopilot.ts:604`), and `logIssue` (`:1062`) and `digest` (`:1107`) read the same memo instead of each doing their own truncated fetch. This is the same intra-tick staleness the process already has — `issueBodies` is cached for the life of the process today, and every filing path uses a distinct key — so nothing changes there. 3. **Page the three open-issue lists** at `tools/autopilot.ts:214`, `:646` and `:890`. 4. Keep the local substring scan and the `state=all` scope exactly as they are, and keep the comment at `tools/autopilot.ts:600-604` (extend it to say the list is paged, and why `q=` is still not the answer). 5. **Make the file testable.** It currently has no exports and a bare top-level dispatcher (`tools/autopilot.ts:1193-1221`) that reads `process.argv` and throws without `FORGEJO_TOKEN`, so importing it runs the autopilot. Wrap that block in `if (import.meta.main)` and `export` the functions under test. This is a mechanical change to the entrypoint; the four command paths must behave identically when run as `bun tools/autopilot.ts <cmd>`. 6. **Add `tools/autopilot.test.ts`** — stub `globalThis.fetch` with a fake issue list of >50 entries carrying a marker on an old one, and assert `alreadyFiled` finds it, that the walk stops on a short page, and that it issues more than one request. Leave `shippedRecently` (`tools/autopilot.ts:821`) at `limit=30`: it is label-filtered and cut to 7 days, and 16 such issues exist. Note it in the paging comment as a deliberate window, not an oversight. ## Acceptance criteria - [ ] `alreadyFiled` walks every page of `/issues?state=all&type=issues` and finds a marker on an issue more than 50 issues old. - [ ] Given today's board (103 issues), `alreadyFiled("qa-reverify-206")` returns `true` and `alreadyFiled("log")` returns `true`. - [ ] The full issue list is fetched **once per process**, not once per consumer: `alreadyFiled`, `logIssue` and `digest` share it. - [ ] `logIssue` finds an existing "Autopilot log" issue regardless of its age, so no third one is created. - [ ] The open-issue reads at `tools/autopilot.ts:214`, `:646` and `:890` are paged, so the `maxOpen` gate counts every open autopilot ticket once the board passes 50 open issues. - [ ] The paging loop terminates on a short page and has an explicit page ceiling; it does not loop for ever against a server that always returns a full page. - [ ] Marker matching is still a local substring scan over bodies; no `q=` search is introduced, and the reason recorded at `tools/autopilot.ts:600-604` is preserved. - [ ] `tools/autopilot.test.ts` exists, stubs `fetch`, and fails if the fetch is reverted to a single unpaged request. - [ ] Importing `tools/autopilot.ts` runs no commands and requires no environment variables; `bun tools/autopilot.ts status`, `ship`, `generate` and `tick` still dispatch as before. - [ ] The commit body says what the cap is, that it had already fired twice (#210/#266 and #181/#257), and why `q=` is not the fix. ## Verification ```bash bun test tools/autopilot.test.ts bun tools/autopilot.ts status # must still print config/state and exit 0 ``` Live read-only check, with `FORGEJO_TOKEN` sourced from `~/.config/cartopolis/forgejo.env` (never `status`-safe to print — it echoes config, not the token): ```bash # the cap itself: both of these return 50 curl -s -H "Authorization: token $FORGEJO_TOKEN" \ "$FORGEJO_API/repos/$FORGEJO_REPO/issues?state=all&type=issues&limit=100" | jq length # the paged walk: 103 today, #284 down to #1 for p in 1 2 3; do curl -s -H "Authorization: token $FORGEJO_TOKEN" \ "$FORGEJO_API/repos/$FORGEJO_REPO/issues?state=all&type=issues&limit=50&page=$p" | jq length; done ``` A dry end-to-end check on the real board is `bun tools/autopilot.ts status` plus reading the log line from `generate` — but `generate` files tickets, so do not run it against production to test this. Nothing here needs a GPU, a workstation, `--shot` or `cargo shots`. ## Out of scope - **Merging or closing the duplicate log issue.** #181 and #257 both exist; after this fix `logIssue` keeps using whichever is newest-first (#257) and makes no more. Reconciling the two is a maintainer's call on the board, not a code change, and this branch should not touch either issue. - **Closing #266 or #210.** The duplicate QA pass already happened and #266 produced #268. - **A CI lane for the test.** There is no bun step anywhere in `.forgejo/workflows/` and no `package.json` in the repo, so gating this would mean adding `setup-bun` or a runner-image change — a host concern, larger than the bug. The test is run by hand with the command above. - **#239's separate bug** — the frontier refill key always being `frontier-refill-0`. Same key family, different defect; fixing the paging does not fix it and this branch should not fold it in. - **`shippedRecently`'s `limit=30`** (`tools/autopilot.ts:821`), which is a deliberate 7-day window over a label-filtered list. - **Any change to what the markers are or where they live.** Bodies stay the store, `state=all` stays the scope. - A general retry/backoff or rate-limit policy for `api`. ## Open questions None. --- Branch: `fix/281-autopilot-page-issue-lists` <details><summary>Original request</summary> Found by the 7-day retrospective on #279, while working out why one ticket was QA'd twice. ## What happens `alreadyFiled` is the autopilot's only guard against filing the same ticket twice. Every filing path goes through it: the red-`main` ticket, the frontier refills, the QA re-verifies and the QA rotation. ```ts async function alreadyFiled(key: string): Promise<boolean> { if (!issueBodies) { const all = await api(`/issues?state=all&type=issues&limit=100`); issueBodies = all.map((i: any) => `${i.body ?? ""}`); } return issueBodies.some((b) => b.includes(`autopilot:${key}`)); } ``` `tools/autopilot.ts:606`. **Forgejo caps a page at 50 regardless of what `limit` asks for.** Measured against this server today: ``` GET /repos/jeroen/cartopolis/issues?state=all&type=issues&limit=100 → 50 issues, #279 down to #222 ``` So `alreadyFiled` answers "no" for every key older than the 50 most recent issues, and there is no second page. Every dedupe key in the system silently expires after 50 issues. Nothing logs it, because from the function's point of view the key genuinely is not there. ## The instance **#206 was QA'd twice.** Both tickets carry the same marker: - **#210** — *QA: what #206 shipped* — filed 2026-08-30, `<!-- autopilot:qa-reverify-206 -->` - **#266** — *QA: what #206 shipped* — filed 2026-09-02, `<!-- autopilot:qa-reverify-206 -->` 56 issues apart, which is past the window. The second pass cost a full session and re-walked ground the first had already covered. (It was not wasted — it filed #268 — but nothing chose to spend a session that way.) The same hole is open under the others: - `main is red on <sha>` can be filed twice for one commit once 50 issues have gone by, and its comment path says "a ticket for it exists — waiting", so a duplicate does not just cost a ticket, it un-parks a stop. - `frontier-refill-N` and the dated `qa-<target>-<date>` keys expire the same way. - #239 records a *different* bug in the same key family — the refill key is always `frontier-refill-0`. Between the two, the frontier's memory is doubly unreliable. ## What would fix it Page it, the way any other list in this file would have to be. `limit=50` and `page=n` until a short page comes back, or read only what is needed: the marker is a fixed string, so Forgejo's issue search (`q=`) answers the same question in one request without holding 50 bodies in memory. A test is available and cheap: assert that a key present on an issue more than 50 issues old is still found. ## Where the seam is `tools/autopilot.ts:606` — `alreadyFiled`. The `issueBodies` cache above it is fine; it is the single unpaged fetch that fills it. <sub>Filed by the 7-day retrospective (#279).</sub> </details> <sub>🤖 Refined by the viberfox issue agent. Reply with **@agent refine** and what is wrong to have this rewritten.</sub>
Author
Collaborator

🤖 Refining this before anything gets built — reading the code, no branch, no commits. If it turns out I'd have to guess at the requirements I'll stop and ask instead.

🤖 Refining this before anything gets built — reading the code, no branch, no commits. If it turns out I'd have to guess at the requirements I'll stop and ask instead.
Author
Collaborator

🤖 Refined. The description above is now the spec; I'll pick it up for implementation on the next poll.

If the direction is wrong, say so now — @agent refine with a correction rewrites it, and agent:skip stops me.


🤖 viberfox issue agent on the VPS — 15 turns · $1.68 · session 09fdaa3a-dd01-425a-b3e7-654e02b8b2f7. Reply with @agent to continue this session.

🤖 **Refined.** The description above is now the spec; I'll pick it up for implementation on the next poll. If the direction is wrong, say so now — **@agent refine** with a correction rewrites it, and `agent:skip` stops me. --- <sub>🤖 viberfox issue agent on the VPS — 15 turns · $1.68 · session `09fdaa3a-dd01-425a-b3e7-654e02b8b2f7`. Reply with **@agent** to continue this session.</sub>
Author
Collaborator

🤖 Picked this up — working on branch fix/281-autopilot-page-issue-lists in a fresh worktree on the VPS. I'll report back here when I'm done.

Label an issue agent:skip to keep me off it, or driven to keep it in a session you are driving.

🤖 Picked this up — working on branch `fix/281-autopilot-page-issue-lists` in a fresh worktree on the VPS. I'll report back here when I'm done. <sub>Label an issue `agent:skip` to keep me off it, or `driven` to keep it in a session you are driving.</sub>
Author
Collaborator

I fixed the paging and pushed the branch.

What changed — one file, tools/autopilot.ts, plus a new test file. No Rust, no crate touched.

The autopilot read the issue list with a single request asking for 100 issues. Forgejo answers that with 50 and does not say it truncated, so any autopilot:<key> marker older than the 50 newest issues was invisible and the lane happily filed the ticket again. Confirmed against the live board today: the unpaged request returns 50 issues (#284 down to #231); walking it 50 at a time over three pages returns all 103.

  • Added pagedApi, which walks a list 50 rows at a time until a short page comes back, with a 40-page stop so a misbehaving server cannot spin for ever.
  • The whole board is now read once per run and shared by the three things that need it — the duplicate check, the daily-note lookup and the digest.
  • The three open-issue reads are paged too, so the "how many tickets are open" limit counts them all. There are 46 open today, four short of where it would have started undercounting.
  • q= search is still not used, and the comment explaining why is kept.
  • The command dispatcher at the bottom is wrapped in if (import.meta.main) and the tested functions are exported, so importing the file runs nothing and needs no credentials.

One thing I found while verifying. A plain text search cannot tell an issue that quotes a marker from one that carries it, and issue #281 — the ticket for this bug — quotes autopilot:log in its body. Newest first, it won, so today's daily note would have been posted onto the bug report instead of onto #257. The log lookup now checks the title as well. I left the duplicate check alone: the same ambiguity there only ever declines to file something twice.

How I verified it. bun test tools/autopilot.test.ts — 7 tests pass; putting the single unpaged request back fails 4 of them. bun tools/autopilot.ts status still prints configuration and state and exits 0. Against the real board, read-only: 103 issues walked, alreadyFiled("qa-reverify-206") and alreadyFiled("log") both return true, and the log lookup now returns #257 "Autopilot log". I did not run generate or tick — those file tickets.

Not verified. There is no bun step in CI, so nothing runs this test automatically; it is a hand-run command, as the ticket says. Nothing here renders, so no visual check is needed anywhere.

For you to decide. tools/autopilot.ts is a fenced path, so the lane will not ship this itself — it needs a person to land it. And the running autopilot uses its own copy of tools/, so the fix takes effect when that copy is refreshed; worth confirming that happens. #181 and #257 both still exist and this branch does not touch either, as the ticket asked.

Branch fix/281-autopilot-page-issue-lists (pushed)

537894e fix(autopilot): page every issue list, so a dedupe key does not expire

🤖 viberfox issue agent on the VPS — 46 turns · $4.27 · session c194dddc-e424-4421-b6f3-88274c957e47. Reply with @agent to continue this session.

I fixed the paging and pushed the branch. **What changed** — one file, `tools/autopilot.ts`, plus a new test file. No Rust, no crate touched. The autopilot read the issue list with a single request asking for 100 issues. Forgejo answers that with 50 and does not say it truncated, so any `autopilot:<key>` marker older than the 50 newest issues was invisible and the lane happily filed the ticket again. Confirmed against the live board today: the unpaged request returns 50 issues (#284 down to #231); walking it 50 at a time over three pages returns all 103. - Added `pagedApi`, which walks a list 50 rows at a time until a short page comes back, with a 40-page stop so a misbehaving server cannot spin for ever. - The whole board is now read once per run and shared by the three things that need it — the duplicate check, the daily-note lookup and the digest. - The three open-issue reads are paged too, so the "how many tickets are open" limit counts them all. There are 46 open today, four short of where it would have started undercounting. - `q=` search is still not used, and the comment explaining why is kept. - The command dispatcher at the bottom is wrapped in `if (import.meta.main)` and the tested functions are exported, so importing the file runs nothing and needs no credentials. **One thing I found while verifying.** A plain text search cannot tell an issue that *quotes* a marker from one that *carries* it, and issue #281 — the ticket for this bug — quotes `autopilot:log` in its body. Newest first, it won, so today's daily note would have been posted onto the bug report instead of onto #257. The log lookup now checks the title as well. I left the duplicate check alone: the same ambiguity there only ever declines to file something twice. **How I verified it.** `bun test tools/autopilot.test.ts` — 7 tests pass; putting the single unpaged request back fails 4 of them. `bun tools/autopilot.ts status` still prints configuration and state and exits 0. Against the real board, read-only: 103 issues walked, `alreadyFiled("qa-reverify-206")` and `alreadyFiled("log")` both return true, and the log lookup now returns #257 "Autopilot log". I did not run `generate` or `tick` — those file tickets. **Not verified.** There is no bun step in CI, so nothing runs this test automatically; it is a hand-run command, as the ticket says. Nothing here renders, so no visual check is needed anywhere. **For you to decide.** `tools/autopilot.ts` is a fenced path, so the lane will not ship this itself — it needs a person to land it. And the running autopilot uses its own copy of `tools/`, so the fix takes effect when that copy is refreshed; worth confirming that happens. #181 and #257 both still exist and this branch does not touch either, as the ticket asked. **Branch** `fix/281-autopilot-page-issue-lists` (pushed) ``` 537894e fix(autopilot): page every issue list, so a dedupe key does not expire ``` --- <sub>🤖 viberfox issue agent on the VPS — 46 turns · $4.27 · session `c194dddc-e424-4421-b6f3-88274c957e47`. Reply with **@agent** to continue this session.</sub>
Author
Collaborator

🤖 Marked agent:done without a CI verdict on 537894e5 — no workflow reported on this commit. The branch is waiting for review; check the run yourself before landing it.

🤖 Marked `agent:done` **without a CI verdict** on `537894e5` — no workflow reported on this commit. The branch is waiting for review; check the run yourself before landing it.
Author
Collaborator

🤖 2026-09-04

🤖 **2026-09-04** - **Landed:** #279, #276, #269, #266, #263, #258 - **Filed:** #289, #288, #284, #283, #282, #281, #280, #279, #278 - **Being built:** #284, #283, #282, #280, #272, #271, #270 - **Finished, shipping when CI passes:** #281, #237 - **Stuck, waiting for you:** #289, #288, #278 - **`main`:** green on `6473ddb1` - **Login:** 18 days left - **Settings:** 1/day, 2 at once, shipping on
Author
Collaborator

🤖 2026-09-05

  • Landed: none
  • Filed: none
  • Being built: #284, #283, #282, #280, #272, #271, #270
  • Finished, shipping when CI passes: #281, #237
  • Stuck, waiting for you: #289, #288, #278
  • main: green on 856f666b
  • Login: 17 days left
  • Settings: 1/day, 2 at once, shipping on
🤖 **2026-09-05** - **Landed:** none - **Filed:** none - **Being built:** #284, #283, #282, #280, #272, #271, #270 - **Finished, shipping when CI passes:** #281, #237 - **Stuck, waiting for you:** #289, #288, #278 - **`main`:** green on `856f666b` - **Login:** 17 days left - **Settings:** 1/day, 2 at once, shipping on
Author
Collaborator

🤖 2026-09-06

🤖 **2026-09-06** - **Landed:** none - **Filed:** #290 - **Being built:** #284, #283, #282, #280, #272, #271, #270 - **Finished, shipping when CI passes:** #290, #281, #237 - **Stuck, waiting for you:** #289, #288, #278 - **`main`:** green on `1204b670` - **Login:** 16 days left - **Settings:** 1/day, 2 at once, shipping on
Author
Collaborator

🤖 2026-09-07

🤖 **2026-09-07** - **Landed:** none - **Filed:** none - **Being built:** #284, #283, #282, #280, #272, #271, #270 - **Finished, shipping when CI passes:** #290, #281, #237 - **Stuck, waiting for you:** #289, #288, #278 - **`main`:** green on `1204b670` - **Login:** 15 days left - **Settings:** 1/day, 2 at once, shipping on
Author
Collaborator

🤖 2026-09-08

🤖 **2026-09-08** - **Landed:** none - **Filed:** none - **Being built:** #284, #283, #282, #280, #272, #271, #270 - **Finished, shipping when CI passes:** #290, #281, #237 - **Stuck, waiting for you:** #289, #288, #278 - **`main`:** green on `1204b670` - **Login:** 14 days left - **Settings:** 1/day, 2 at once, shipping on
Author
Collaborator

🤖 2026-09-09

🤖 **2026-09-09** - **Landed:** none - **Filed:** none - **Being built:** #284, #283, #282, #280, #272, #271, #270 - **Finished, shipping when CI passes:** #290, #281, #237 - **Stuck, waiting for you:** #289, #288, #278 - **`main`:** green on `cd5d1b84` - **Login:** 13 days left - **Settings:** 1/day, 2 at once, shipping on
Author
Collaborator

🤖 2026-09-10

  • Landed: #294, #292, #291, #290, #289, #288, #284, #283, #282, #281, #280, #278, #272, #271, #270
  • Filed: #294, #293, #292, #291
  • Being built: none
  • Finished, shipping when CI passes: none
  • Stuck, waiting for you: #293
  • main: green on 03512806
  • Login: 12 days left
  • Settings: 1/day, 2 at once, shipping on
  • Queue: 0 row(s) waiting, 1 filed — focus: “Improving performance on the steamdeck. keep it concrete and simple. use or extend harness”
🤖 **2026-09-10** - **Landed:** #294, #292, #291, #290, #289, #288, #284, #283, #282, #281, #280, #278, #272, #271, #270 - **Filed:** #294, #293, #292, #291 - **Being built:** none - **Finished, shipping when CI passes:** none - **Stuck, waiting for you:** #293 - **`main`:** green on `03512806` - **Login:** 12 days left - **Settings:** 1/day, 2 at once, shipping on - **Queue:** 0 row(s) waiting, 1 filed — focus: “Improving performance on the steamdeck. keep it concrete and simple. use or extend harness”
Author
Collaborator

🤖 2026-09-11

🤖 **2026-09-11** - **Landed:** #296, #294, #292, #291, #290, #289, #288, #284, #283, #282, #281, #280, #278, #272, #271, #270 - **Filed:** #300, #298, #297, #296, #295 - **Being built:** none - **Finished, shipping when CI passes:** none - **Stuck, waiting for you:** #300, #298, #297, #295, #293 - **`main`:** green on `7e4441d9` - **Login:** 11 days left - **Settings:** 1/day, 2 at once, shipping on - **Queue:** 18 row(s) waiting, 2 filed — focus: “Improving performance on the steamdeck. keep it concrete and simple. use or extend harness”
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeroen/cartopolis#281
No description provided.