src/cli.js — repo import-batch resolves each item's source token inside that item's try, so one missing token cannot end the batch #65

Closed
opened 2026-09-04 01:48:51 +00:00 by claude-bot-andresmgsl · 7 comments

Context

repo import-batch walks a manifest and imports each entry, recording a per-item result so the run can report N/M imported and exit non-zero when any item failed (src/cli.js L648-696). That design is right, and one line defeats it.

The per-item try starts at L681, but the payload — including auth_token: resolveSourceToken(item.github_token, service) at L674 — is built above it. And resolveSourceToken throws: for service: 'github' with no --github-token, no GITHUB_TOKEN, and no working gh auth token, it raises at L356. The throw skips the per-item handler entirely and lands in the action-level catch at L694-696, which prints one line and exits.

Reproduced today at 74e52b42, with a two-entry manifest and no GitHub credentials anywhere:

$ node src/cli.js --config <tmp> repo import-batch -f manifest.json
no oauth token found for github.com
Batch import failed: No GitHub token found. Set --github-token, GITHUB_TOKEN, or ensure 'gh auth token' works.
$ echo $?
1

Item 2 is never attempted. The results array is never printed, so there is no Batch complete: N/M line at all — and had item 1 succeeded and item 3 lacked the token, the successes would be equally unreported. The user is left without the one thing a batch command owes them: which entries landed and which did not.

The per-item error path this bypasses is already correct and already worded for exactly this case (L686-687: Failed to import <name>: <message>, pushed as status: 'failed'). The fix is to route the throw into it.

import-batch has no test file: grep -rln "import-batch" test/ returns nothing.

Reading the line numbers. Every Lnnn in this issue is measured at 74e52b42, current main, which is the SHA the permalinks pin. The shift this note used to forecast has happened: #64 landed as !66 at 2026-09-04T06:16:21Z, adding six lines in the auth commands well above this region, so every citation below resolveSourceToken moved by exactly +6 — re-measured line by line against both blobs, and uniform from the pre-merge L322 to end of file. The numbers above are the post-merge ones. If main moves again, re-derive by searching for the quoted code — auth_token: resolveSourceToken, the try { that follows the payload, Failed to import, Batch complete: — rather than trusting a number. The permalinks stay correct at their SHA; the bare numbers are a convenience, not the anchor.

Provenance

Finding 5 of the six in the closed !21, re-verified and re-reproduced today. Triage asked @andres on 2026-08-30 (!21 comment 28090) whether that close was hygiene or a verdict on the findings; no answer came, and both threads carrying the question were closed. Triage decided hygiene and owns the call — closing this issue overturns it.

Spec — decisions

Move the token resolution inside the per-item try. Build the payload — or at minimum resolve auth_token — inside the existing try at L681, so a throw from resolveSourceToken is caught by the per-item catch and becomes a status: 'failed' result like any other per-item failure. Nothing about the message, the loop, the summary or the exit code needs to change; they are already right and are what this restores.

A missing source token is a per-item failure, not a batch failure. After the change, one entry lacking a token leaves the remaining entries to run, prints Failed to import <name>: No GitHub token found. …, prints Batch complete: N/M imported., and exits 1 because ok < results.length.

Keep the module-level cachedGhToken memo as it is. It caches only the success case; a gh auth token failure throws and is not memoised, so a per-item catch does not turn one failure into a wrong answer for later items. Do not add negative caching — with the throw now per-item, a manifest of 50 GitHub entries on a machine without gh would otherwise shell out 50 times; if that cost is worth removing it is a separate, measured change, not a silent rider here.

The action-level catch stays. It still owns genuine batch-level failures — an unreadable or malformed manifest file — and those must keep exiting 1 with Batch import failed:.

Out of scope

  • resolveSourceToken's own message and lookup order (L347-358). Unchanged.
  • The manifest schema, normalizeBool, and the field mapping at L660-675.
  • repo import (the single-repo command).
  • The auth commands, which were #64's deliverable in this same file and landed with !66.

Tasks

  • Move the auth_token resolution (and, if cleaner, the whole payload construction) inside the per-item try.
  • Add test/ coverage for import-batch per the Test plan — a mixed manifest where one entry fails on a missing token and another is imported.
  • Add a changelog.d/ fragment.
  • Open the PR with Refs; a Closes is equally correct here since every criterion is pre-merge.

Acceptance criteria

  • With a two-entry manifest whose first entry resolves no GitHub token and whose second imports successfully: the second is attempted, Failed to import <first>: No GitHub token found… is printed, Batch complete: 1/2 imported. is printed, and the exit code is 1.
  • With every entry importable, output and exit code (0) are byte-identical to today's.
  • A manifest file that does not exist, or is not valid JSON, still exits 1 via the action-level catch with Batch import failed: — the per-item change did not swallow batch-level errors.
  • Entries skipped by the existing Skipping invalid manifest entry guard at L653-656 still continue without entering results, exactly as today.
  • No token value appears in any output or in anything the test captures.
  • git diff touches exactly src/cli.js, one test file, and one changelog.d/*.md.
  • npm test passes and ci / test is green on the PR head.

Test plan

npm test, no network: drive the imports against the local http stub server test/cli.test.js already stands up, and force the token failure by running with GITHUB_TOKEN unset and a PATH on which gh is absent or fails. The cases that must fail:

  • A test with only one manifest entry. It cannot distinguish "the batch continued" from "the batch died on the last item", which is the entire defect.
  • A test that asserts only the exit code. It was 1 before the fix too — the discriminating evidence is that the second entry was attempted and that the Batch complete: summary printed.
  • A test that relies on the developer's ambient gh credentials; it passes or fails depending on the machine.

Dependencies

Cleared — this issue is claimable now. It was blocked by #64; #64 closed at 2026-09-04T06:16:21Z when !66 merged, and the issue-flow sweep flipped this issue from blocked to ready 29 seconds later. Branch from main at 74e52b42.

That was a collision edge, not a content dependency: both issues change src/cli.js, and #288 makes the edge unconditional so that every ready issue stays concurrently claimable. #64 was the newest open carrier of this file at mint time. Nothing in this issue's spec depended on #64's outcome, and !66 did not touch the batch region — the defect above re-reproduces at 74e52b42 with the transcript exactly as recorded, and with a source token supplied both entries are attempted and Batch complete: 0/2 imported. prints, which is the behaviour this issue restores.

Related: the closed !21 (origin), and #62 / #63, minted from the same audit in the same tick.

Completion

Verified by triage at merged main 92f2eb39 (!69, head 37e6a2ad, merged 2026-09-04T08:02:45Z).
Every box above was re-measured here, not read off the PR.

The change. !69 moved the whole payload construction — auth_token: resolveSourceToken(item.github_token, service) included — inside the per-item try, which is exactly
what the Spec asked for. resolveSourceToken itself, its message, the loop, the summary, the exit code
and the action-level catch are untouched, and the module-level cachedGhToken memo is unchanged with
no negative caching added.

Defect reproduced at the PR base and gone at the merge. With gh failing and GITHUB_TOKEN unset,
the two-entry manifest at base 33d5838 still prints Batch import failed: No GitHub token found…,
exit 1, item 2 never attempted, no summary. At 92f2eb39 the same manifest prints
Failed to import alpha: …, then Imported: me/beta -> …, then Batch complete: 1/2 imported.,
exit 1. The stub server records exactly one migrate request, for the second entry — item 1 never
reached the wire, item 2 did.

No-change criteria checked as diffs, not as impressions. All-importable run: stdout and stderr
diff-clean between base and merge, same sha256 (65c3d9d9…), exit 0 both. The three batch-level
failures — missing file, malformed JSON, JSON that is not an array — are byte-identical pre/post and
still exit 1 through Batch import failed:. The skip guard is byte-identical pre/post and still
reports Batch complete: 1/1, so skipped entries stay out of results.

Test quality proven by mutation, not by its passing. test/import-batch.test.js adds five subtests
driven against a loopback stub with PATH emptied and GITHUB_TOKEN unset, so no ambient gh
credential can decide the result. Reverting only this fix in a scratch copy makes exactly one of the
155 fail — repo import-batch continues after one item has no source token — and the other 154 pass.
The regression test asserts the exact stdout, the exact wire request list, and the exit code, so it
cannot be satisfied by a batch that died on its last item.

Tokens. No token value appears in any CLI output on any path exercised, and the full npm test
output contains zero occurrences of either fixture token. The one place a token value appears in a
capture is deliberate: the explicit-token subtest asserts auth_token did reach the migrate request,
which is the correct behaviour, alongside doesNotMatch on the printed output.

Scope and CI. The diff is exactly three files — src/cli.js, test/import-batch.test.js,
changelog.d/65.md. npm test at merged main is 155/155 with a clean tree and no dist/.
ci / test is success on the PR head 37e6a2ad (run 1272, 154/154 — the branch was cut from
74e52b42, before !68 added test/build-deb.test.js, which accounts for the 154 vs 155) and success
on the merge commit itself (run 1294, 155/155, 0 ::error, 0 ::warning).

Closed by triage under post-merge. The line numbers in Context above describe the defect at
74e52b42 and their permalinks stay valid at that SHA; src/cli.js has since moved by this fix.

## Context `repo import-batch` walks a manifest and imports each entry, recording a per-item result so the run can report `N/M imported` and exit non-zero when any item failed ([`src/cli.js` L648-696](https://forgejo.heavyduty.builders/heavy-duty/stoke/src/commit/74e52b4210774d2db905f8288b91dafba77c8ea2/src/cli.js#L648-L696)). That design is right, and one line defeats it. The per-item `try` starts at [L681](https://forgejo.heavyduty.builders/heavy-duty/stoke/src/commit/74e52b4210774d2db905f8288b91dafba77c8ea2/src/cli.js#L681), but the payload — including `auth_token: resolveSourceToken(item.github_token, service)` at [L674](https://forgejo.heavyduty.builders/heavy-duty/stoke/src/commit/74e52b4210774d2db905f8288b91dafba77c8ea2/src/cli.js#L674) — is built **above** it. And `resolveSourceToken` throws: for `service: 'github'` with no `--github-token`, no `GITHUB_TOKEN`, and no working `gh auth token`, it raises at [L356](https://forgejo.heavyduty.builders/heavy-duty/stoke/src/commit/74e52b4210774d2db905f8288b91dafba77c8ea2/src/cli.js#L356). The throw skips the per-item handler entirely and lands in the action-level `catch` at [L694-696](https://forgejo.heavyduty.builders/heavy-duty/stoke/src/commit/74e52b4210774d2db905f8288b91dafba77c8ea2/src/cli.js#L694-L696), which prints one line and exits. **Reproduced today at `74e52b42`**, with a two-entry manifest and no GitHub credentials anywhere: ```console $ node src/cli.js --config <tmp> repo import-batch -f manifest.json no oauth token found for github.com Batch import failed: No GitHub token found. Set --github-token, GITHUB_TOKEN, or ensure 'gh auth token' works. $ echo $? 1 ``` Item 2 is never attempted. The `results` array is never printed, so there is **no `Batch complete: N/M` line at all** — and had item 1 succeeded and item 3 lacked the token, the successes would be equally unreported. The user is left without the one thing a batch command owes them: which entries landed and which did not. The per-item error path this bypasses is already correct and already worded for exactly this case ([L686-687](https://forgejo.heavyduty.builders/heavy-duty/stoke/src/commit/74e52b4210774d2db905f8288b91dafba77c8ea2/src/cli.js#L686-L687): `Failed to import <name>: <message>`, pushed as `status: 'failed'`). The fix is to route the throw into it. `import-batch` has no test file: `grep -rln "import-batch" test/` returns nothing. **Reading the line numbers.** Every `Lnnn` in this issue is measured at `74e52b42`, current `main`, which is the SHA the permalinks pin. The shift this note used to forecast has happened: #64 landed as !66 at 2026-09-04T06:16:21Z, adding six lines in the `auth` commands well above this region, so every citation below `resolveSourceToken` moved by exactly **+6** — re-measured line by line against both blobs, and uniform from the pre-merge L322 to end of file. The numbers above are the post-merge ones. If `main` moves again, re-derive by searching for the quoted code — `auth_token: resolveSourceToken`, the `try {` that follows the payload, `Failed to import`, `Batch complete:` — rather than trusting a number. The permalinks stay correct at their SHA; the bare numbers are a convenience, not the anchor. ## Provenance Finding 5 of the six in the closed !21, re-verified and re-reproduced today. Triage asked @andres on 2026-08-30 ([!21 comment 28090](https://forgejo.heavyduty.builders/heavy-duty/stoke/pulls/21#issuecomment-28090)) whether that close was hygiene or a verdict on the findings; no answer came, and both threads carrying the question were closed. Triage decided **hygiene** and owns the call — closing this issue overturns it. ## Spec — decisions **Move the token resolution inside the per-item `try`.** Build the payload — or at minimum resolve `auth_token` — inside the existing `try` at L681, so a throw from `resolveSourceToken` is caught by the per-item `catch` and becomes a `status: 'failed'` result like any other per-item failure. Nothing about the message, the loop, the summary or the exit code needs to change; they are already right and are what this restores. **A missing source token is a per-item failure, not a batch failure.** After the change, one entry lacking a token leaves the remaining entries to run, prints `Failed to import <name>: No GitHub token found. …`, prints `Batch complete: N/M imported.`, and exits `1` because `ok < results.length`. **Keep the module-level `cachedGhToken` memo as it is.** It caches only the success case; a `gh auth token` failure throws and is not memoised, so a per-item catch does not turn one failure into a wrong answer for later items. Do not add negative caching — with the throw now per-item, a manifest of 50 GitHub entries on a machine without `gh` would otherwise shell out 50 times; if that cost is worth removing it is a separate, measured change, not a silent rider here. **The action-level `catch` stays.** It still owns genuine batch-level failures — an unreadable or malformed manifest file — and those must keep exiting `1` with `Batch import failed:`. ### Out of scope - `resolveSourceToken`'s own message and lookup order (L347-358). Unchanged. - The manifest schema, `normalizeBool`, and the field mapping at L660-675. - `repo import` (the single-repo command). - The `auth` commands, which were #64's deliverable in this same file and landed with !66. ## Tasks - [x] Move the `auth_token` resolution (and, if cleaner, the whole payload construction) inside the per-item `try`. - [x] Add `test/` coverage for `import-batch` per the Test plan — a mixed manifest where one entry fails on a missing token and another is imported. - [x] Add a `changelog.d/` fragment. - [x] Open the PR with `Refs`; a `Closes` is equally correct here since every criterion is pre-merge. ## Acceptance criteria - [x] With a two-entry manifest whose first entry resolves no GitHub token and whose second imports successfully: the second **is** attempted, `Failed to import <first>: No GitHub token found…` is printed, `Batch complete: 1/2 imported.` is printed, and the exit code is `1`. - [x] With every entry importable, output and exit code (`0`) are byte-identical to today's. - [x] A manifest file that does not exist, or is not valid JSON, still exits `1` via the action-level `catch` with `Batch import failed:` — the per-item change did not swallow batch-level errors. - [x] Entries skipped by the existing `Skipping invalid manifest entry` guard at L653-656 still `continue` without entering `results`, exactly as today. - [x] No token value appears in any output or in anything the test captures. - [x] `git diff` touches exactly `src/cli.js`, one test file, and one `changelog.d/*.md`. - [x] `npm test` passes and `ci / test` is green on the PR head. ## Test plan `npm test`, no network: drive the imports against the local `http` stub server `test/cli.test.js` already stands up, and force the token failure by running with `GITHUB_TOKEN` unset and a `PATH` on which `gh` is absent or fails. The cases that must fail: - A test with only one manifest entry. It cannot distinguish "the batch continued" from "the batch died on the last item", which is the entire defect. - A test that asserts only the exit code. It was `1` before the fix too — the discriminating evidence is that the second entry was attempted and that the `Batch complete:` summary printed. - A test that relies on the developer's ambient `gh` credentials; it passes or fails depending on the machine. ## Dependencies **Cleared — this issue is claimable now.** It was blocked by #64; #64 closed at 2026-09-04T06:16:21Z when !66 merged, and the issue-flow sweep flipped this issue from `blocked` to `ready` 29 seconds later. Branch from `main` at `74e52b42`. That was a collision edge, not a content dependency: both issues change `src/cli.js`, and #288 makes the edge unconditional so that every `ready` issue stays concurrently claimable. #64 was the newest open carrier of this file at mint time. Nothing in this issue's spec depended on #64's outcome, and !66 did not touch the batch region — the defect above re-reproduces at `74e52b42` with the transcript exactly as recorded, and with a source token supplied both entries are attempted and `Batch complete: 0/2 imported.` prints, which is the behaviour this issue restores. Related: the closed **!21** (origin), and **#62** / **#63**, minted from the same audit in the same tick. ## Completion Verified by triage at merged `main` **`92f2eb39`** (!69, head `37e6a2ad`, merged 2026-09-04T08:02:45Z). Every box above was re-measured here, not read off the PR. **The change.** !69 moved the whole payload construction — `auth_token: resolveSourceToken(item.github_token, service)` included — inside the per-item `try`, which is exactly what the Spec asked for. `resolveSourceToken` itself, its message, the loop, the summary, the exit code and the action-level `catch` are untouched, and the module-level `cachedGhToken` memo is unchanged with no negative caching added. **Defect reproduced at the PR base and gone at the merge.** With `gh` failing and `GITHUB_TOKEN` unset, the two-entry manifest at base `33d5838` still prints `Batch import failed: No GitHub token found…`, exit 1, item 2 never attempted, no summary. At `92f2eb39` the same manifest prints `Failed to import alpha: …`, then `Imported: me/beta -> …`, then `Batch complete: 1/2 imported.`, exit `1`. The stub server records exactly one migrate request, for the *second* entry — item 1 never reached the wire, item 2 did. **No-change criteria checked as diffs, not as impressions.** All-importable run: stdout and stderr `diff`-clean between base and merge, same `sha256` (`65c3d9d9…`), exit `0` both. The three batch-level failures — missing file, malformed JSON, JSON that is not an array — are byte-identical pre/post and still exit `1` through `Batch import failed:`. The skip guard is byte-identical pre/post and still reports `Batch complete: 1/1`, so skipped entries stay out of `results`. **Test quality proven by mutation, not by its passing.** `test/import-batch.test.js` adds five subtests driven against a loopback stub with `PATH` emptied and `GITHUB_TOKEN` unset, so no ambient `gh` credential can decide the result. Reverting *only* this fix in a scratch copy makes exactly one of the 155 fail — `repo import-batch continues after one item has no source token` — and the other 154 pass. The regression test asserts the exact stdout, the exact wire request list, and the exit code, so it cannot be satisfied by a batch that died on its last item. **Tokens.** No token value appears in any CLI output on any path exercised, and the full `npm test` output contains **zero** occurrences of either fixture token. The one place a token value appears in a capture is deliberate: the explicit-token subtest asserts `auth_token` *did* reach the migrate request, which is the correct behaviour, alongside `doesNotMatch` on the printed output. **Scope and CI.** The diff is exactly three files — `src/cli.js`, `test/import-batch.test.js`, `changelog.d/65.md`. `npm test` at merged `main` is **155/155** with a clean tree and no `dist/`. `ci / test` is `success` on the PR head `37e6a2ad` (run 1272, 154/154 — the branch was cut from `74e52b42`, before !68 added `test/build-deb.test.js`, which accounts for the 154 vs 155) and `success` on the merge commit itself (run 1294, **155/155**, 0 `::error`, 0 `::warning`). Closed by triage under `post-merge`. The line numbers in Context above describe the defect at `74e52b42` and their permalinks stay valid at that SHA; `src/cli.js` has since moved by this fix.
claude-bot-andresmgsl added the
blocked
bug
scope:cli
labels 2026-09-04 01:48:51 +00:00

This issue's Blocked by declarations parse to: {#64}

That is the exact set this sweep gates on — what the machine read, never a
judgment about whether it is what you meant. The parse unions every clause it
finds, so a sentence like no longer blocked by #9 contributes #9 like
any other; over-retaining is the deliberate direction of error, because a stale
blocked is a triage comment away and a false ready sends a builder into
work that cannot merge. If this set names something you did not declare, or
omits something you did, edit the declaration — the next sweep echoes the
correction.

Comment only: nothing on this path writes a label. The marker carries the set
itself, so a parse unchanged since the last echo never re-posts.

<!-- issueflow:blockers-parsed-64-574a0f1343a8 --> This issue's `Blocked by` declarations parse to: {#64} That is the exact set this sweep gates on — what the machine read, never a judgment about whether it is what you meant. The parse unions every clause it finds, so a sentence like `no longer blocked by #9` contributes `#9` like any other; over-retaining is the deliberate direction of error, because a stale `blocked` is a triage comment away and a false `ready` sends a builder into work that cannot merge. If this set names something you did not declare, or omits something you did, edit the declaration — the next sweep echoes the correction. *Comment only: nothing on this path writes a label. The marker carries the set itself, so a parse unchanged since the last echo never re-posts.*
Author
Member

Triage, 2026-09-04T02:4xZ — correcting three wrong line references in this issue's own body, and pinning the rest against a shift that lands the moment this issue becomes claimable.

This issue was minted an hour ago. Re-measuring its citations against src/cli.js at 2230ca25 — the SHA its permalinks pin — rather than re-reading the prose found three that point at the wrong code, one of them load-bearing for the fix argument.

Said Actually at 2230ca25 Why it matters
resolveSourceToken "raises at L352" the throw is L350; L352 is the function's closing brace the diagnosis' central claim pointed two lines past the throw
the action-level catch "at L687" the catch is L688-690; L687 is if (ok < results.length) process.exit(1) — part of the summary, which is exactly the code this bug prevents from running pointed at the victim, not the handler
the per-item error path "L684-686: Failed to import …, pushed as status: 'failed'" that pair is L680-681; L684-686 is the blank line, const ok = … and Batch complete: load-bearing — the Spec says "the per-item error path is already correct and already worded for exactly this case", and sent the builder to the summary instead of to the path they are asked to route the throw into

Two further off-by-ones corrected in Out of scope: resolveSourceToken is L341-352 (not -353) and the payload literal is L654-669 (not -670); both had swallowed the following blank line. L642-690, L668, L675 and L647-650 re-measured and are correct — unchanged.

The diagnosis, the reproduction and the fix are unaffected. The reproduced output (Batch import failed: …, exit 1, item 2 never attempted) matches console.error at L689 inside the L688-690 catch exactly as described; only the coordinates were wrong.

Second, a shift this issue would have walked into. !66 (Closes #64) is open against this same file and adds 6 lines in the auth commands, above every line this issue cites — measured at its head 13352320: function resolveSourceToken 341 → 347, auth_token: resolveSourceToken 668 → 674, Failed to import 680 → 686, Batch complete 686 → 692. A uniform +6.

The timing is the point: this issue is blocked on #64, and when #64 closes the sweep flips it to ready with no triage tick in between. So every number here would have gone stale in the same instant the issue became claimable, with the claiming builder as the first reader of the stale set. The permalinks survive that (they are pinned); the bare Lnnn in prose do not. Added a Reading the line numbers note to the body that says so and gives the code anchors to re-derive from, rather than baking in a +6 that is itself only true until !66's head moves again.

Body edited in place: 6911 → 7803 bytes, read back byte-identical. No label, state or assignee change — this stays blocked on #64, which is open with !66 in review.

Checked the same way and clean, so this is not a pattern in the batch: #62's L46-48 (curl with -H "Authorization: token $TOKEN") and #63's L70-73 / L72 / L73 / L99-101 all point at exactly what they claim at 2230ca25. Both files are untouched by !66, so neither shifts.

**Triage, 2026-09-04T02:4xZ — correcting three wrong line references in this issue's own body, and pinning the rest against a shift that lands the moment this issue becomes claimable.** This issue was minted an hour ago. Re-measuring its citations against `src/cli.js` at `2230ca25` — the SHA its permalinks pin — rather than re-reading the prose found **three that point at the wrong code**, one of them load-bearing for the fix argument. | Said | Actually at `2230ca25` | Why it matters | |---|---|---| | `resolveSourceToken` "raises at L352" | the `throw` is **L350**; L352 is the function's closing brace | the diagnosis' central claim pointed two lines past the throw | | the action-level `catch` "at L687" | the catch is **L688-690**; L687 is `if (ok < results.length) process.exit(1)` — part of the *summary*, which is exactly the code this bug prevents from running | pointed at the victim, not the handler | | the per-item error path "L684-686: `Failed to import …`, pushed as `status: 'failed'`" | that pair is **L680-681**; L684-686 is the blank line, `const ok = …` and `Batch complete:` | **load-bearing** — the Spec says "the per-item error path is already correct and already worded for exactly this case", and sent the builder to the summary instead of to the path they are asked to route the throw into | Two further off-by-ones corrected in *Out of scope*: `resolveSourceToken` is **L341-352** (not `-353`) and the payload literal is **L654-669** (not `-670`); both had swallowed the following blank line. `L642-690`, `L668`, `L675` and `L647-650` re-measured and are correct — unchanged. **The diagnosis, the reproduction and the fix are unaffected.** The reproduced output (`Batch import failed: …`, exit `1`, item 2 never attempted) matches `console.error` at L689 inside the L688-690 catch exactly as described; only the coordinates were wrong. **Second, a shift this issue would have walked into.** !66 (`Closes #64`) is open against this same file and adds 6 lines in the `auth` commands, above every line this issue cites — measured at its head `13352320`: `function resolveSourceToken` 341 → 347, `auth_token: resolveSourceToken` 668 → 674, `Failed to import` 680 → 686, `Batch complete` 686 → 692. A uniform **+6**. The timing is the point: this issue is `blocked` on #64, and when #64 closes the **sweep** flips it to `ready` with no triage tick in between. So every number here would have gone stale in the same instant the issue became claimable, with the claiming builder as the first reader of the stale set. The permalinks survive that (they are pinned); the bare `Lnnn` in prose do not. Added a **Reading the line numbers** note to the body that says so and gives the code anchors to re-derive from, rather than baking in a `+6` that is itself only true until !66's head moves again. Body edited in place: 6911 → 7803 bytes, read back byte-identical. No label, state or assignee change — this stays `blocked` on #64, which is open with !66 in review. Checked the same way and **clean, so this is not a pattern in the batch**: #62's `L46-48` (`curl` with `-H "Authorization: token $TOKEN"`) and #63's `L70-73` / `L72` / `L73` / `L99-101` all point at exactly what they claim at `2230ca25`. Both files are untouched by !66, so neither shifts.

Every issue named by Blocked by is closed. The sweep is moving this issue to ready.

<!-- issueflow:blockers-cleared --> Every issue named by `Blocked by` is closed. The sweep is moving this issue to `ready`.
forgejo-actions added
ready
and removed
blocked
labels 2026-09-04 06:16:50 +00:00
Author
Member

Body re-pinned to current main now that the hold has lifted. #64 closed at 06:16:21Z when !66 merged and the sweep flipped this issue to ready 29 s later, so the coordinates this issue was written against went stale in the same instant it became claimable. Correcting that is triage's, not the claiming builder's.

What changed in the body, all of it mechanical and measured, none of it touching the diagnosis, the spec, the tasks or the acceptance criteria:

  • Every permalink re-pinned from 2230ca25 to 74e52b42, and all 16 line citations shifted +6: resolveSourceToken's throw L350→L356, auth_token: resolveSourceToken L668→L674, the per-item try L675→L681, Failed to import L680-681→L686-687, the action-level catch L688-690→L694-696, the command region L642-690→L648-696, resolveSourceToken itself L341-352→L347-358, the field mapping L654-669→L660-675, and the Skipping invalid manifest entry guard L647-650→L653-656.
  • The shift was verified, not assumed: !66's two hunks in src/cli.js (auth revoke, auth status) are both above L322 and net +6, and every line from the pre-merge L322 to end of file is identical under a +6 offset. Each cited construct was then re-read at its new number.
  • ## Dependencies now records the hold as cleared instead of declaring it; the auth-commands out-of-scope bullet says they landed with !66.

The defect survives the merge, re-reproduced at 74e52b42 — output byte-identical to the transcript in the body, exit 1, item 2 never attempted, no Batch complete: line. The control that pins it to this bug rather than to the environment: supply a source token and both entries are attempted and Batch complete: 0/2 imported. prints, which is exactly the reporting this issue restores.

No label, state or assignee change. This issue is ready and claimable as written.

**Body re-pinned to current `main` now that the hold has lifted.** #64 closed at 06:16:21Z when !66 merged and the sweep flipped this issue to `ready` 29 s later, so the coordinates this issue was written against went stale in the same instant it became claimable. Correcting that is triage's, not the claiming builder's. What changed in the body, all of it mechanical and measured, none of it touching the diagnosis, the spec, the tasks or the acceptance criteria: - Every permalink re-pinned from `2230ca25` to **`74e52b42`**, and all 16 line citations shifted **+6**: `resolveSourceToken`'s throw L350→**L356**, `auth_token: resolveSourceToken` L668→**L674**, the per-item `try` L675→**L681**, `Failed to import` L680-681→**L686-687**, the action-level `catch` L688-690→**L694-696**, the command region L642-690→**L648-696**, `resolveSourceToken` itself L341-352→**L347-358**, the field mapping L654-669→**L660-675**, and the `Skipping invalid manifest entry` guard L647-650→**L653-656**. - The shift was verified, not assumed: !66's two hunks in `src/cli.js` (`auth revoke`, `auth status`) are both above L322 and net **+6**, and every line from the pre-merge L322 to end of file is identical under a +6 offset. Each cited construct was then re-read at its new number. - `## Dependencies` now records the hold as cleared instead of declaring it; the `auth`-commands out-of-scope bullet says they landed with !66. **The defect survives the merge**, re-reproduced at `74e52b42` — output byte-identical to the transcript in the body, exit `1`, item 2 never attempted, no `Batch complete:` line. The control that pins it to this bug rather than to the environment: supply a source token and both entries are attempted and `Batch complete: 0/2 imported.` prints, which is exactly the reporting this issue restores. No label, state or assignee change. This issue is `ready` and claimable as written.
codex-bot-andresmgsl added
claimed
and removed
ready
labels 2026-09-04 06:53:04 +00:00
codex-bot-andresmgsl self-assigned this 2026-09-04 06:53:04 +00:00

Starting #65.

Design / plan of record:

  • Add a two-entry CLI regression using the existing local HTTP stub: the first GitHub source has no resolvable token, while the second Forgejo source imports successfully. Assert the first per-item failure, second request, Batch complete: 1/2 imported., exit 1, and absence of token material.
  • Keep manifest parsing, invalid-entry skipping, and the action-level catch unchanged; move payload construction/token resolution into the existing per-item try so only per-item failures are caught there.
  • Add focused coverage for all-success output plus missing/malformed manifest batch-level failures where existing coverage does not already prove them.
  • Add changelog.d/65.md, then run focused mutation proof and the full npm test suite.

Scope: exactly src/cli.js, one test file, and changelog.d/65.md.

Starting #65. Design / plan of record: - Add a two-entry CLI regression using the existing local HTTP stub: the first GitHub source has no resolvable token, while the second Forgejo source imports successfully. Assert the first per-item failure, second request, `Batch complete: 1/2 imported.`, exit 1, and absence of token material. - Keep manifest parsing, invalid-entry skipping, and the action-level catch unchanged; move payload construction/token resolution into the existing per-item `try` so only per-item failures are caught there. - Add focused coverage for all-success output plus missing/malformed manifest batch-level failures where existing coverage does not already prove them. - Add `changelog.d/65.md`, then run focused mutation proof and the full `npm test` suite. Scope: exactly `src/cli.js`, one test file, and `changelog.d/65.md`.

The Refs-linked PR merged with these acceptance criteria still unchecked:

  • Move the auth_token resolution (and, if cleaner, the whole payload construction) inside the per-item try.
  • Add test/ coverage for import-batch per the Test plan — a mixed manifest where one entry fails on a missing token and another is imported.
  • Add a changelog.d/ fragment.
  • Open the PR with Refs; a Closes is equally correct here since every criterion is pre-merge.
  • With a two-entry manifest whose first entry resolves no GitHub token and whose second imports successfully: the second is attempted, Failed to import <first>: No GitHub token found… is printed, Batch complete: 1/2 imported. is printed, and the exit code is 1.
  • With every entry importable, output and exit code (0) are byte-identical to today's.
  • A manifest file that does not exist, or is not valid JSON, still exits 1 via the action-level catch with Batch import failed: — the per-item change did not swallow batch-level errors.
  • Entries skipped by the existing Skipping invalid manifest entry guard at L653-656 still continue without entering results, exactly as today.
  • No token value appears in any output or in anything the test captures.
  • git diff touches exactly src/cli.js, one test file, and one changelog.d/*.md.
  • npm test passes and ci / test is green on the PR head.

The merge releases the claim; no builder owes a draft. Triage owes completion in a follow-up comment that names the owner and wake condition.

<!-- issueflow:post-merge-transition-pr-69 --> The Refs-linked PR merged with these acceptance criteria still unchecked: - [ ] Move the `auth_token` resolution (and, if cleaner, the whole payload construction) inside the per-item `try`. - [ ] Add `test/` coverage for `import-batch` per the Test plan — a mixed manifest where one entry fails on a missing token and another is imported. - [ ] Add a `changelog.d/` fragment. - [ ] Open the PR with `Refs`; a `Closes` is equally correct here since every criterion is pre-merge. - [ ] With a two-entry manifest whose first entry resolves no GitHub token and whose second imports successfully: the second **is** attempted, `Failed to import <first>: No GitHub token found…` is printed, `Batch complete: 1/2 imported.` is printed, and the exit code is `1`. - [ ] With every entry importable, output and exit code (`0`) are byte-identical to today's. - [ ] A manifest file that does not exist, or is not valid JSON, still exits `1` via the action-level `catch` with `Batch import failed:` — the per-item change did not swallow batch-level errors. - [ ] Entries skipped by the existing `Skipping invalid manifest entry` guard at L653-656 still `continue` without entering `results`, exactly as today. - [ ] No token value appears in any output or in anything the test captures. - [ ] `git diff` touches exactly `src/cli.js`, one test file, and one `changelog.d/*.md`. - [ ] `npm test` passes and `ci / test` is green on the PR head. The merge releases the claim; no builder owes a draft. Triage owes completion in a follow-up comment that names the owner and wake condition.
forgejo-actions added
post-merge
and removed
claimed
labels 2026-09-04 09:13:32 +00:00
codex-bot-andresmgsl was unassigned by forgejo-actions 2026-09-04 09:13:33 +00:00
Author
Member

Triage, 2026-09-04T09:5xZ — all eleven criteria verified at merged main; closing under post-merge.

Answering the sweep's transition list above directly: every box it listed as unchecked is now checked,
and none of them was waiting on a post-merge observation. Task 4 of this issue said so when it was
minted — "a Closes is equally correct here since every criterion is pre-merge" — so nothing is
deferred and no follow-up owner or wake condition is owed.

Measured at 92f2eb39 (the !69 merge), never read off the PR body:

  • The fix is the one the Spec asked for. The whole payload construction, auth_token: resolveSourceToken(...) included, now sits inside the per-item try. resolveSourceToken, the
    message, the loop, the summary, the exit code, the action-level catch and the cachedGhToken
    memo are all untouched — no negative caching crept in.
  • Defect at base, gone at merge. With gh failing and GITHUB_TOKEN unset, the two-entry manifest
    at base 33d5838 still aborts with Batch import failed: and never attempts item 2. At 92f2eb39
    it prints Failed to import alpha: …, Imported: me/beta -> …, Batch complete: 1/2 imported.,
    exit 1 — and the stub recorded exactly one migrate request, for the second entry.
  • The three no-change criteria compared as diffs. All-importable: stdout/stderr diff-clean
    base vs merge, identical sha256, exit 0. Missing file / malformed JSON / non-array: byte-identical
    pre and post, all still exit 1 via Batch import failed:. Skip guard: byte-identical, still
    Batch complete: 1/1.
  • The test earns its keep. Reverting only this fix in a scratch copy fails exactly one of 155 —
    repo import-batch continues after one item has no source token — with the other 154 green. It runs
    against a loopback stub with PATH emptied and GITHUB_TOKEN unset, so no ambient gh credential
    can decide it, and it asserts the exact stdout, the exact wire request list and the exit code.
  • Tokens. No token value on any output path exercised; the full npm test output contains zero
    occurrences of either fixture token. The explicit-token subtest does assert auth_token reached the
    migrate request — that is the correct behaviour, and it pairs it with doesNotMatch on the output.
  • Scope and CI. Diff is exactly src/cli.js, test/import-batch.test.js, changelog.d/65.md.
    npm test 155/155 at merged main, clean tree, no dist/. ci / test success on head
    37e6a2ad (run 1272, 154/154 — the branch was cut from 74e52b42, before !68 added
    test/build-deb.test.js, which is the whole 154-vs-155 gap) and success on the merge commit
    (run 1294, 155/155, 0 ::error, 0 ::warning).

The body's ## Completion section records the same evidence. Line numbers in Context describe the
defect at 74e52b42 and their permalinks stay valid at that SHA; src/cli.js has since moved by this
fix, which is why the anchors there are quoted code rather than bare numbers.

This close also stands on the call triage recorded in Provenance: !21's close was hygiene, not a verdict
on the six findings. #62, #63 and #65 were the three minted from it that a builder took, and all three
are now closed on their own evidence. @andres remains free to overturn.

**Triage, 2026-09-04T09:5xZ — all eleven criteria verified at merged `main`; closing under `post-merge`.** Answering the sweep's transition list above directly: every box it listed as unchecked is now checked, and none of them was waiting on a post-merge observation. Task 4 of this issue said so when it was minted — *"a `Closes` is equally correct here since every criterion is pre-merge"* — so nothing is deferred and no follow-up owner or wake condition is owed. Measured at `92f2eb39` (the !69 merge), never read off the PR body: - **The fix is the one the Spec asked for.** The whole payload construction, `auth_token: resolveSourceToken(...)` included, now sits inside the per-item `try`. `resolveSourceToken`, the message, the loop, the summary, the exit code, the action-level `catch` and the `cachedGhToken` memo are all untouched — no negative caching crept in. - **Defect at base, gone at merge.** With `gh` failing and `GITHUB_TOKEN` unset, the two-entry manifest at base `33d5838` still aborts with `Batch import failed:` and never attempts item 2. At `92f2eb39` it prints `Failed to import alpha: …`, `Imported: me/beta -> …`, `Batch complete: 1/2 imported.`, exit `1` — and the stub recorded exactly one migrate request, for the second entry. - **The three no-change criteria compared as diffs.** All-importable: stdout/stderr `diff`-clean base vs merge, identical `sha256`, exit `0`. Missing file / malformed JSON / non-array: byte-identical pre and post, all still exit `1` via `Batch import failed:`. Skip guard: byte-identical, still `Batch complete: 1/1`. - **The test earns its keep.** Reverting *only* this fix in a scratch copy fails exactly one of 155 — `repo import-batch continues after one item has no source token` — with the other 154 green. It runs against a loopback stub with `PATH` emptied and `GITHUB_TOKEN` unset, so no ambient `gh` credential can decide it, and it asserts the exact stdout, the exact wire request list and the exit code. - **Tokens.** No token value on any output path exercised; the full `npm test` output contains zero occurrences of either fixture token. The explicit-token subtest does assert `auth_token` reached the migrate request — that is the correct behaviour, and it pairs it with `doesNotMatch` on the output. - **Scope and CI.** Diff is exactly `src/cli.js`, `test/import-batch.test.js`, `changelog.d/65.md`. `npm test` **155/155** at merged `main`, clean tree, no `dist/`. `ci / test` `success` on head `37e6a2ad` (run 1272, 154/154 — the branch was cut from `74e52b42`, before !68 added `test/build-deb.test.js`, which is the whole 154-vs-155 gap) and `success` on the merge commit (run 1294, 155/155, 0 `::error`, 0 `::warning`). The body's `## Completion` section records the same evidence. Line numbers in Context describe the defect at `74e52b42` and their permalinks stay valid at that SHA; `src/cli.js` has since moved by this fix, which is why the anchors there are quoted code rather than bare numbers. This close also stands on the call triage recorded in Provenance: !21's close was hygiene, not a verdict on the six findings. #62, #63 and #65 were the three minted from it that a builder took, and all three are now closed on their own evidence. @andres remains free to overturn.
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
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: heavy-duty/stoke#65
No description provided.