Skip to content

[quality] coverage-e2e names auth.ts:24 as the only uncovered line on main; getErrorDetails' non-object arm is unreachable through dist/ (Octokit always rejects a RequestError) #507

Open

Description

@hivecommons-hive

Finding

npm run test:coverage:e2e on main @ 1062ce7 (local run, 78 files / 487 tests) reports 99.95% lines / 97.96% branches overall, and names exactly one uncovered line in the whole run: src/utils/auth.ts 24 (99.15% lines).

File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
auth.ts   |   99.17 |     95.4 |     100 |   99.15 | 24

Line 24 is the non-object return of getErrorDetails:

function getErrorDetails(error: unknown): { status: unknown, message: string } {
  if (typeof error === 'object' && error !== null) {
    …
    return { status, message }
  }

  return {                     // ← auth.ts:24-27, never reached from dist/index.js
    status: 'unknown',
    message: String(error),
  }
}

All three callers (checkOrgMember auth.ts:56, checkCollaborator auth.ts:91, the issue-comments read auth.ts:137) hand it the rejection of an Octokit call. Through the bundle that rejection is always an object: @octokit/request wraps a failed fetch in new RequestError(message, 500, …) (fetch-wrapper.js:35-53) and every non-2xx response in RequestError as well (fetch-wrapper.js:85-99). A primitive rejection cannot be produced by any HTTP response or network fault msw can simulate, so no bundle test can drive the arm.

Unit coverage (npx vitest run --coverage, same revision, 157 files / 2116 tests) is 100% lines; the arm is reached only by __tests__/utils/auth.test.ts:93, which injects it directly with vi.spyOn(octokit.orgs, 'checkMembershipForUser').mockRejectedValueOnce('socket hang up') — a spy, not an input the action can see.

This is the same shape as #466 (the ?? await getPull fallback in tide.ts, dropped by #492): a defensive arm that only the unit suite reaches, that keeps a line in the e2e "Uncovered Line #s" column.

Recommendation

Follow #492: drop the non-object branch from getErrorDetails (treat the argument as object, since every caller passes an Octokit rejection) and remove the auth.test.ts:93 case that only reaches it; repack dist/. If the defensive arm is wanted for the hypothetical non-Octokit throw, the alternative is a /* v8 ignore next 4 */ on the return, as 7a1df7a did for the reset*() hooks — either way the e2e report stops naming a line that no input reaches.

  • getErrorDetails non-object return removed (or v8-ignored); dist/ repacked
  • npm run test:coverage:e2e no longer lists auth.ts:24

This is a production-code change, so no PR accompanies this issue. Remaining e2e residuals on main are branch folds only (?? '', ?., instanceof Error ? … : String(e)), several of which are already claimed by open PRs #497, #498, #504 and #505; they are out of scope here.

Priority

  • Impact: low
  • Effort: low

Filed by quality agent (hold-gated mode)


🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 1062ce7

— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

Activity

  1. added
    testingCreated by Hive for agent-filed issue provenance
    agent/qualityCreated by Hive for agent-filed issue provenance
    on Oct 17, 2026
  2. github-actions commented on Oct 17, 2026

    @github-actions
    Contributor

    Please add a kind label with /kind feature, /kind failing-test or /kind cleanup.

  3. hivecommons-hive commented on Oct 17, 2026

    @hivecommons-hive
    ContributorAuthor

    Folded finding. This was filed as a separate issue, but 1 open agent-filed issue (plus 3 closed as completed in the last 14 days) already cite tide.ts, so it was folded into the oldest of them instead of opening another. Treat this issue as the tracker for that component: prefer one structural fix that covers every folded variant over a PR per variant.

    [quality] coverage-e2e: the '?? ""' folds for an absent comment.body (comments.ts:58, requireMatchingLabel.ts:248) and label.name (trigger.ts:181) never run through dist/index.js

    Finding

    npm run test:coverage:e2e on main @ 1062ce7 (local run, 78 files / 487 tests; coverage-e2e/lcov.info) reports three ?? '' folds that stand in for optional REST fields with zero hits through dist/index.js, while the unit suite (npx vitest run --coverage, same revision, 157 files / 2116 tests) covers all three files at 100 % lines / 100 % branches:

    file line fold lcov (e2e)
    src/utils/comments.ts (createCommentOnce) 58 (comment.body ?? '').includes(marker) BRDA:58,2,1,0
    src/plugins/requireMatchingLabel.ts (botCommentsWithMarker) 248 (comment.body ?? '').includes(marker) BRDA:248,32,1,0
    src/issueComment/trigger.ts (okToTestOnPullRequest) 181 String(label?.name ?? '') BRDA:181,29,1,0

    The REST schema types issue-comment.body as optional and the object form of issue.labels[] with an optional name (@octokit/openapi-types), so an empty bot comment or a label object without a name is a real input. Every bundle scenario, though, serves body on each listed comment and name on each label object (stringLabelArm.test.ts reaches the nameless-object arm only in getCurrentLabels, labeling.ts:144-149, not in trigger.ts), so none of the three folds has run through the packed bundle.

    Unlike the instanceof Error ? … : String(e) residuals (which only a non-Octokit throw reaches), these three are reachable from served API data, the same shape as #498 (draft ?? false, files ?? [], labels ?? []) and #505 (issue.user null). They are disjoint from the open held PRs #497 (approve.ts, meow.ts), #498 (tide.ts, sweep.ts), #504 (octokit.ts), #505 (issueComment/approve.ts, close.ts, reopen.ts).

    Recommendation

    Add one bundle test file that serves the fields absent and drives dist/index.js:

    • pull_request synchronize whose payload labels is [{ id: 2 }, { name: 'ok-to-test' }]: the nameless object is skipped and the runs on the head are still approved; and [{ id: 2 }] alone: ::debug::trigger: the pull request does not carry ok-to-test, no actions/runs read.
    • pull_request labeled lgtm by a bot (no binding) whose GET /issues/1/comments lists one bot comment with no body: the stale-lgtm notice is still posted (createCommentOnce does not treat the bodyless comment as already carrying the marker).
    • issues labeled kind/bug with needs-kind present whose comments list holds a bodyless bot comment and a marker comment: only the marker comment is deleted.
    • npm run test:coverage:e2e no longer lists BRDA:58,2,1,0, BRDA:248,32,1,0 or BRDA:181,29,1,0

    Priority

    • Impact: medium (unit-covered, not e2e-covered)
    • Effort: low

    Filed by quality agent (hold-gated mode)


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 1062ce7

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88


    Folded finding. This was filed as a separate issue, but 1 open agent-filed issue (plus 6 closed as completed in the last 14 days) already cite src/utils/auth.ts, so it was folded into the oldest of them instead of opening another. Treat this issue as the tracker for that component: prefer one structural fix that covers every folded variant over a PR per variant.

    [quality] unit coverage's four remaining 'uncovered' branches (close.ts:39, reopen.ts:36, approve.ts:503, auth.ts:220) are negative-count BRDA artifacts of coverage-v8, not gaps

    Finding

    Every "uncovered branch" the unit run still reports on main @ 1062ce7 except approve.ts:415 (claimed by #497) is a reporting artifact, not a gap. npx vitest run --coverage (157 files / 2116 tests, 100 % lines, 99.7 % branches) lists:

    close.ts    | 100 |    90 | 100 | 100 | 39
    reopen.ts   | 100 |  87.5 | 100 | 100 | 36
    approve.ts  | 100 | 98.87 | 100 | 100 | 415,503
    auth.ts     | 100 | 98.85 | 100 | 100 | 220
    

    coverage/lcov.info shows the "missed" arm of each of these four ifs with a negative hit count:

    SF:src/issueComment/close.ts   BRDA:39,3,0,12   BRDA:39,3,1,-3
    SF:src/issueComment/reopen.ts  BRDA:36,3,0,9    BRDA:36,3,1,-1
    SF:src/plugins/approve.ts      BRDA:503,84,0,7  BRDA:503,84,1,-6
    SF:src/utils/auth.ts           BRDA:220,13,0,94 BRDA:220,13,1,-51
    

    All four are the same shape — an if whose condition variable was just assigned from a multi-line expression containing await:

    const isAuthUser = isAuthor || (await checkCollaborator(…)) || (await closePolicyAllows(…))
    if (isAuthUser) {                                   // close.ts:39, reopen.ts:36
    
    const hasOwners = typeof base === 'string' ? await branchHasOwners(…) : await repoHasOwners(…)
    if (!hasOwners) {                                   // approve.ts:503
    
    const hasOwners = context.payload.issue?.pull_request !== undefined ? await assertPullRequestOwner(…) : await assertRootOwner(…)
    if (!hasOwners) {                                   // auth.ts:220

    The arms reported negative are exercised: __tests__/issueCommentTest/close.test.ts:55,139 and reopen.test.ts:55 drive the refusing path; __tests__/plugins/approveEvents.test.ts:237-244 asserts 'approve: the base branch has no OWNERS files'; every owners-authorized command drives hasOwners === true at auth.ts:220. An LCOV BRDA hit count must be a non-negative integer or -, so these records are also malformed for any consumer of the lcovonly artifact.

    The e2e run (npm run test:coverage:e2e, 78 files / 487 tests) shows the identical negative pairs for approve.ts:503 (-26) and auth.ts:220 (-43), so the artifact is independent of the vite-vs-tsc transform. Re-running the unit suite on PR #503's dependency set (vitest / @vitest/coverage-v8 5.0.4) reproduces the same four records, so the pending bump does not clear it. Searching upstream surfaces vitest-dev/vitest#9725 and #11476 (NaN / negative BRDA counts from the AST-based V8→Istanbul remap) as apparently the same class; I have not reduced a minimal reproduction against them.

    Why it matters: these four lines are what keep the unit branch figure at 99.7 % rather than 100 %, they sit in the "Uncovered Line #s" column that #507, #466 and the open coverage PRs triage from, and #500 proposes a 99 % branch floor that is now partly spent on phantom misses.

    Recommendation

    One deliverable: record the artifact so it stops being re-triaged, and decide where the fix should land.

    • docs/contributing.md coverage section notes that close.ts:39, reopen.ts:36, approve.ts:503, auth.ts:220 are negative-count BRDA artifacts of the v8 provider (an if following an awaited multi-line condition), with the lcov.info grep above as the recognizer
    • a minimal reproduction is filed upstream (vitest-dev/vitest) or the existing report is linked from the note

    No code or test change is proposed: the arms are covered, and a /* v8 ignore */ would hide real misses on the same lines. Not opening a PR for the docs note because docs/contributing.md is occupied by #500 (its line 48 coverage paragraph); it can be folded into that PR or follow it.

    Priority

    • Impact: low
    • Effort: low

    Filed by quality agent (hold-gated mode)


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 1062ce7

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88


    Folded finding. This was filed as a separate issue, but 1 open agent-filed issue (plus 3 closed as completed in the last 14 days) already cite __tests__/utils/auth.test.ts, so it was folded into the oldest of them instead of opening another. Treat this issue as the tracker for that component: prefer one structural fix that covers every folded variant over a PR per variant.

    [quality] /remove happy-path tests (remove.test.ts:25,47) assert nothing — a first-label-only mutant survives; enable test/expect-expect

    Finding

    __tests__/label/remove.test.ts has two happy-path tests, removes the specified label (line 25) and removes multiple labels (line 47), that register msw DELETE …/issues/1/labels/<name> handlers, call handleIssueComment, and then assert nothing. They are the only two assertion-free tests among the 1656 it/test bodies under __tests__/ (the other four candidates a body scan turned up — bundle/holdSweepSettings.test.ts:91,99,107 and utils/pullRequestOwners.test.ts:264 — assert through expectWindow/expectRequests/ObserveRequest.called()).

    The gap is real, not cosmetic. On main @ 88241f4, with this mutant in src/labels/remove.ts:

    await removeLabels(octokit, context, issueNumber, toRemove.slice(0, 1))

    npx vitest run __tests__/label/remove.test.ts → 6 passed. Dropping the call entirely is caught only by the 500 arm (fails the action when a label removal fails), not by either happy-path test. No other suite pins the success path: bundle/bundle.test.ts:814 drives /remove foo into a 500, bundle/prefixedRemoveArms.test.ts:88-104 drives only the error arms, label/errorPaths.test.ts:59-70 only the rejections. So /remove a b silently removing one label would ship green — unit coverage is 100 % lines on this revision, which is exactly why line coverage cannot see it.

    Why it was not caught structurally: @vitest/eslint-plugin's test/expect-expect ships with @antfu/eslint-config (which eslint.config.mjs uses) but is not in the active rule set (npx eslint --print-config __tests__/utils/auth.test.ts lists test/consistent-test-it, no-identical-title, no-import-node-test, no-only-tests, prefer-hooks-in-order, prefer-lowercase-title only). Trial-enabling it with the repository's assertion helpers whitelisted flags exactly these two tests and nothing else:

    'test/expect-expect': ['error', {
      assertFunctionNames: ['expect', 'expect*', '*.called', '*.notCalled'],
    }],

    (Dropping *.called/*.notCalled from the list adds pullRequestOwners.test.ts:265, which asserts via observeBranch.called() — so each entry is load-bearing.)

    Recommendation

    One PR, two files:

    • __tests__/label/remove.test.ts: attach an utils.ObserveRequest to each DELETE handler in both tests and await observeReq.called() for each (two observers in the multi-label test), plus expect(setFailed).not.toHaveBeenCalled(); the slice(0, 1) mutant above must then fail
    • eslint.config.mjs: enable test/expect-expect for __tests__/**/*.test.ts with the assertFunctionNames above so npm run lint (already in build-test) refuses the next assertion-free test

    Decision taken by hive: enable the rule scoped to test files with a whitelist of the existing helper names rather than rewriting the helpers to call expect internally — additive, no helper or test semantics change. docs/contributing.md is left alone (occupied by #511).

    Priority

    • Impact: medium (a real /remove regression passes CI; not a coverage gap — lines are 100 % covered)
    • Effort: low

    Filed by quality agent (hold-gated mode)


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 88241f4

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  4. hivecommons-hive commented on Oct 18, 2026

    @hivecommons-hive
    ContributorAuthor

    Full inventory of the same-class residuals on main @ 1062ce7

    Re-ran both suites locally on main @ 1062ce7 so the one structural fix this tracker asks for can cover every variant at once rather than one PR per line:

    • unit: npx vitest run --coverage — 157 files / 2116 tests, 100 % lines, 99.7 % branches (coverage/lcov.info)
    • e2e: npm run test:coverage:e2e — 78 files / 487 tests, 99.95 % lines, 97.96 % branches (coverage-e2e/lcov.info)

    Of the 34 zero-hit e2e branches, 13 are already claimed by open held PRs (#497 approve.ts 228/230/409/415 + meow.ts 88; #498 tide.ts 258/426 + sweep.ts 141; #504 octokit.ts 8; #505 issueComment/approve.ts 94; #508 comments.ts 58 + requireMatchingLabel.ts 248 + trigger.ts 181; #509 approve.ts 101). Every remaining one is the same shape as auth.ts:24: a defensive arm for a non-Error / non-object rejection that only a unit-test spy or a direct mockRejectedValueOnce('string') can produce, while every real caller forwards an Octokit RequestError / GraphqlResponseError, so no served API response or network fault drives it through dist/index.js:

    file line arm lcov (e2e)
    src/utils/auth.ts 14, 15, 17 (→ return at 24) getErrorDetails non-object / status-less / message-less BRDA:14,0,1,0 BRDA:15,2,1,0 BRDA:17,3,1,0
    src/run.ts 40 core.setFailed(error instanceof Error ? … : String(error)) BRDA:40,2,1,0
    src/issueComment/handleIssueComment.ts 217 normalizeError new Error(String(error)) BRDA:217,25,1,0
    src/issueComment/trigger.ts 323 statusOf non-object → undefined BRDA:323,49,1,0
    src/labels/lgtm.ts 68 e instanceof Error ? e.message : String(e) into refuse BRDA:68,8,1,0
    src/cronJobs/labelSync.ts 107 same BRDA:107,8,1,0
    src/cronJobs/sweep.ts 132 same BRDA:132,11,1,0
    src/plugins/approveReview.ts 226, 230 errorStatus non-number / errorMessage String(e) BRDA:226,22,1,0 BRDA:230,24,1,0
    src/plugins/requireMatchingLabel.ts 167 e instanceof Error ? e.message : e BRDA:167,23,1,0
    src/plugins/tide.ts 137, 138 fetchMergeability catch: non-number status / String(e) BRDA:137,3,1,0 BRDA:138,5,1,0
    src/utils/config.ts 657 parseDuration rethrow : e arm (parseDuration only throws TypeError) BRDA:657,83,1,0
    src/utils/events.ts 50 runEventHandlers new Error(String(e)) BRDA:50,3,1,0
    src/utils/mergeQueue.ts 190 errorMessage String(e) (graphql rejections are GraphqlResponseError) BRDA:190,13,1,0
    src/utils/command.ts 23 getLineArgs .at(-1) ?? '' BRDA:23,0,1,0

    command.ts:23 is dead for a different reason: getLineArgs is only called by /milestone and /retitle (milestone.ts:43, retitle.ts:31), both reached only after handleIssueComment.ts:128 has matched the very same command with hasCommand, and neither has an alias in commandAliases (handleIssueComment.ts:54-62), so findCommandArgs(...).at(-1) is never undefined there.

    All of these are unit-covered (100 % lines on the same revision), so none is a coverage gap in its own right; they are the lines that keep the e2e "Uncovered" column non-empty. Whatever is chosen for auth.ts:24 — drop the arm (as #492 did) or /* v8 ignore */ it (as 7a1df7a did for the reset*() hooks) — the same treatment applied to this list brings npm run test:coverage:e2e to 100 % branches outside the folds the held PRs above already drive. No test-only PR can land this, so none is opened here.

    The only unit-side residual, auth.ts:220 (BRDA:220,13,1,-51), is a negative-count remap artifact reported identically by both lcov files (-43 in e2e) and is not a real uncovered branch.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 1062ce7

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  5. hivecommons-hive commented on Oct 18, 2026

    @hivecommons-hive
    ContributorAuthor

    task-list sweep: 0 of 2 items ticked. Not closing yet — outstanding boxes remain.

    Outstanding items:

    • 🔲 getErrorDetails non-object return removed (or v8-ignored); dist/ repacked
    • 🔲 npm run test:coverage:e2e no longer lists auth.ts:24

    Not yet touched: src/utils/auth.ts, fetch-wrapper.js, __tests__/utils/auth.test.ts, tide.ts, auth.test.ts, dist/, auth.ts

    Merged PRs referencing this issue so far:

    This comment is edited in place by the task-list sweep on every cycle; it is not duplicated.

  6. hivecommons-hive commented on Oct 18, 2026

    @hivecommons-hive
    ContributorAuthor

    Verified open on main @ 88241f4

    Re-ran npm run test:coverage:e2e locally on main @ 88241f4 (after #508 and #509 merged): 83 files / 502 tests pass, 99.95 % lines / 98.79 % branches overall, and src/utils/auth.ts 24 is still the only uncovered line in the run:

    All files | 99.96 | 98.79 | 100 | 99.95 |
    auth.ts   | 99.17 |  95.4 | 100 | 99.15 | 24
    

    src/utils/auth.ts:13-28 still carries the non-object return { status: 'unknown', message: String(error) } arm with no /* v8 ignore */, and __tests__/utils/auth.test.ts:93 still reaches it only through mockRejectedValueOnce('socket hang up').

    #508 ticked the four boxes of the folded ?? '' finding (comments.ts:58, requireMatchingLabel.ts:248, trigger.ts:181 now all driven through dist/index.js); the issue's own two boxes remain open.

    What is left needs a human (or a lane permitted to change src/): dropping or /* v8 ignore next 4 */-ing the getErrorDetails fallback is a production-code change plus a dist/ repack, which the quality lane does not PR. Either option is mechanical:

    Then npm run pack and commit dist/. Labelled hive/verified-open.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 88241f4

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityCreated by Hive for agent-filed issue provenancehive/partially-addressedA merged PR addressed only part of this issue; remainder still actionablehive/verified-openneeds-kindstage/040-covered-by-prAn open PR covers this issuetestingCreated by Hive for agent-filed issue provenance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions