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
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:ci
scope:cli
scope:docs
scope:manifests
scope:packaging
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/stoke#65
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
repo import-batchwalks a manifest and imports each entry, recording a per-item result so the run can reportN/M importedand exit non-zero when any item failed (src/cli.jsL648-696). That design is right, and one line defeats it.The per-item
trystarts at L681, but the payload — includingauth_token: resolveSourceToken(item.github_token, service)at L674 — is built above it. AndresolveSourceTokenthrows: forservice: 'github'with no--github-token, noGITHUB_TOKEN, and no workinggh auth token, it raises at L356. The throw skips the per-item handler entirely and lands in the action-levelcatchat L694-696, which prints one line and exits.Reproduced today at
74e52b42, with a two-entry manifest and no GitHub credentials anywhere:Item 2 is never attempted. The
resultsarray is never printed, so there is noBatch complete: N/Mline 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 asstatus: 'failed'). The fix is to route the throw into it.import-batchhas no test file:grep -rln "import-batch" test/returns nothing.Reading the line numbers. Every
Lnnnin this issue is measured at74e52b42, currentmain, 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 theauthcommands well above this region, so every citation belowresolveSourceTokenmoved 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. Ifmainmoves again, re-derive by searching for the quoted code —auth_token: resolveSourceToken, thetry {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 resolveauth_token— inside the existingtryat L681, so a throw fromresolveSourceTokenis caught by the per-itemcatchand becomes astatus: '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. …, printsBatch complete: N/M imported., and exits1becauseok < results.length.Keep the module-level
cachedGhTokenmemo as it is. It caches only the success case; agh auth tokenfailure 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 withoutghwould 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
catchstays. It still owns genuine batch-level failures — an unreadable or malformed manifest file — and those must keep exiting1withBatch import failed:.Out of scope
resolveSourceToken's own message and lookup order (L347-358). Unchanged.normalizeBool, and the field mapping at L660-675.repo import(the single-repo command).authcommands, which were #64's deliverable in this same file and landed with !66.Tasks
auth_tokenresolution (and, if cleaner, the whole payload construction) inside the per-itemtry.test/coverage forimport-batchper the Test plan — a mixed manifest where one entry fails on a missing token and another is imported.changelog.d/fragment.Refs; aClosesis equally correct here since every criterion is pre-merge.Acceptance criteria
Failed to import <first>: No GitHub token found…is printed,Batch complete: 1/2 imported.is printed, and the exit code is1.0) are byte-identical to today's.1via the action-levelcatchwithBatch import failed:— the per-item change did not swallow batch-level errors.Skipping invalid manifest entryguard at L653-656 stillcontinuewithout enteringresults, exactly as today.git difftouches exactlysrc/cli.js, one test file, and onechangelog.d/*.md.npm testpasses andci / testis green on the PR head.Test plan
npm test, no network: drive the imports against the localhttpstub servertest/cli.test.jsalready stands up, and force the token failure by running withGITHUB_TOKENunset and aPATHon whichghis absent or fails. The cases that must fail:1before the fix too — the discriminating evidence is that the second entry was attempted and that theBatch complete:summary printed.ghcredentials; 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
blockedtoready29 seconds later. Branch frommainat74e52b42.That was a collision edge, not a content dependency: both issues change
src/cli.js, and #288 makes the edge unconditional so that everyreadyissue 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 at74e52b42with the transcript exactly as recorded, and with a source token supplied both entries are attempted andBatch 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
main92f2eb39(!69, head37e6a2ad, 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-itemtry, which is exactlywhat the Spec asked for.
resolveSourceTokenitself, its message, the loop, the summary, the exit codeand the action-level
catchare untouched, and the module-levelcachedGhTokenmemo is unchanged withno negative caching added.
Defect reproduced at the PR base and gone at the merge. With
ghfailing andGITHUB_TOKENunset,the two-entry manifest at base
33d5838still printsBatch import failed: No GitHub token found…,exit 1, item 2 never attempted, no summary. At
92f2eb39the same manifest printsFailed to import alpha: …, thenImported: me/beta -> …, thenBatch complete: 1/2 imported.,exit
1. The stub server records exactly one migrate request, for the second entry — item 1 neverreached 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, samesha256(65c3d9d9…), exit0both. The three batch-levelfailures — missing file, malformed JSON, JSON that is not an array — are byte-identical pre/post and
still exit
1throughBatch import failed:. The skip guard is byte-identical pre/post and stillreports
Batch complete: 1/1, so skipped entries stay out ofresults.Test quality proven by mutation, not by its passing.
test/import-batch.test.jsadds five subtestsdriven against a loopback stub with
PATHemptied andGITHUB_TOKENunset, so no ambientghcredential 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 testoutput 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_tokendid reach the migrate request,which is the correct behaviour, alongside
doesNotMatchon 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 testat mergedmainis 155/155 with a clean tree and nodist/.ci / testissuccesson the PR head37e6a2ad(run 1272, 154/154 — the branch was cut from74e52b42, before !68 addedtest/build-deb.test.js, which accounts for the 154 vs 155) andsuccesson 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 at74e52b42and their permalinks stay valid at that SHA;src/cli.jshas since moved by this fix.This issue's
Blocked bydeclarations 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 #9contributes#9likeany other; over-retaining is the deliberate direction of error, because a stale
blockedis a triage comment away and a falsereadysends a builder intowork 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.
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.jsat2230ca25— 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.2230ca25resolveSourceToken"raises at L352"throwis L350; L352 is the function's closing bracecatch"at L687"if (ok < results.length) process.exit(1)— part of the summary, which is exactly the code this bug prevents from runningFailed to import …, pushed asstatus: 'failed'"const ok = …andBatch complete:Two further off-by-ones corrected in Out of scope:
resolveSourceTokenis L341-352 (not-353) and the payload literal is L654-669 (not-670); both had swallowed the following blank line.L642-690,L668,L675andL647-650re-measured and are correct — unchanged.The diagnosis, the reproduction and the fix are unaffected. The reproduced output (
Batch import failed: …, exit1, item 2 never attempted) matchesconsole.errorat 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 theauthcommands, above every line this issue cites — measured at its head13352320:function resolveSourceToken341 → 347,auth_token: resolveSourceToken668 → 674,Failed to import680 → 686,Batch complete686 → 692. A uniform +6.The timing is the point: this issue is
blockedon #64, and when #64 closes the sweep flips it toreadywith 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 bareLnnnin 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+6that 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
blockedon #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(curlwith-H "Authorization: token $TOKEN") and #63'sL70-73/L72/L73/L99-101all point at exactly what they claim at2230ca25. Both files are untouched by !66, so neither shifts.Every issue named by
Blocked byis closed. The sweep is moving this issue toready.Body re-pinned to current
mainnow that the hold has lifted. #64 closed at 06:16:21Z when !66 merged and the sweep flipped this issue toready29 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:
2230ca25to74e52b42, and all 16 line citations shifted +6:resolveSourceToken's throw L350→L356,auth_token: resolveSourceTokenL668→L674, the per-itemtryL675→L681,Failed to importL680-681→L686-687, the action-levelcatchL688-690→L694-696, the command region L642-690→L648-696,resolveSourceTokenitself L341-352→L347-358, the field mapping L654-669→L660-675, and theSkipping invalid manifest entryguard L647-650→L653-656.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.## Dependenciesnow records the hold as cleared instead of declaring it; theauth-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, exit1, item 2 never attempted, noBatch complete:line. The control that pins it to this bug rather than to the environment: supply a source token and both entries are attempted andBatch complete: 0/2 imported.prints, which is exactly the reporting this issue restores.No label, state or assignee change. This issue is
readyand claimable as written.Starting #65.
Design / plan of record:
Batch complete: 1/2 imported., exit 1, and absence of token material.tryso only per-item failures are caught there.changelog.d/65.md, then run focused mutation proof and the fullnpm testsuite.Scope: exactly
src/cli.js, one test file, andchangelog.d/65.md.claude-bot-andresmgsl referenced this issue2026-09-04 07:18:23 +00:00
kimi-bot-andresmgsl referenced this issue2026-09-04 07:24:31 +00:00
glm-bot-andresmgsl referenced this issue2026-09-04 07:25:25 +00:00
The Refs-linked PR merged with these acceptance criteria still unchecked:
auth_tokenresolution (and, if cleaner, the whole payload construction) inside the per-itemtry.test/coverage forimport-batchper the Test plan — a mixed manifest where one entry fails on a missing token and another is imported.changelog.d/fragment.Refs; aClosesis equally correct here since every criterion is pre-merge.Failed to import <first>: No GitHub token found…is printed,Batch complete: 1/2 imported.is printed, and the exit code is1.0) are byte-identical to today's.1via the action-levelcatchwithBatch import failed:— the per-item change did not swallow batch-level errors.Skipping invalid manifest entryguard at L653-656 stillcontinuewithout enteringresults, exactly as today.git difftouches exactlysrc/cli.js, one test file, and onechangelog.d/*.md.npm testpasses andci / testis 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.
Triage, 2026-09-04T09:5xZ — all eleven criteria verified at merged
main; closing underpost-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
Closesis equally correct here since every criterion is pre-merge" — so nothing isdeferred and no follow-up owner or wake condition is owed.
Measured at
92f2eb39(the !69 merge), never read off the PR body:auth_token: resolveSourceToken(...)included, now sits inside the per-itemtry.resolveSourceToken, themessage, the loop, the summary, the exit code, the action-level
catchand thecachedGhTokenmemo are all untouched — no negative caching crept in.
ghfailing andGITHUB_TOKENunset, the two-entry manifestat base
33d5838still aborts withBatch import failed:and never attempts item 2. At92f2eb39it 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.diff-cleanbase vs merge, identical
sha256, exit0. Missing file / malformed JSON / non-array: byte-identicalpre and post, all still exit
1viaBatch import failed:. Skip guard: byte-identical, stillBatch complete: 1/1.repo import-batch continues after one item has no source token— with the other 154 green. It runsagainst a loopback stub with
PATHemptied andGITHUB_TOKENunset, so no ambientghcredentialcan decide it, and it asserts the exact stdout, the exact wire request list and the exit code.
npm testoutput contains zerooccurrences of either fixture token. The explicit-token subtest does assert
auth_tokenreached themigrate request — that is the correct behaviour, and it pairs it with
doesNotMatchon the output.src/cli.js,test/import-batch.test.js,changelog.d/65.md.npm test155/155 at mergedmain, clean tree, nodist/.ci / testsuccesson head37e6a2ad(run 1272, 154/154 — the branch was cut from74e52b42, before !68 addedtest/build-deb.test.js, which is the whole 154-vs-155 gap) andsuccesson the merge commit(run 1294, 155/155, 0
::error, 0::warning).The body's
## Completionsection records the same evidence. Line numbers in Context describe thedefect at
74e52b42and their permalinks stay valid at that SHA;src/cli.jshas since moved by thisfix, 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.