Skip to content

fix: only warn on endpoint-less sessionAuth when it carries more than refreshOn (#148) - #149

Merged
garretpremo merged 2 commits into
devfrom
issues/148-refreshon-only-warning
Aug 4, 2026
Merged

fix: only warn on endpoint-less sessionAuth when it carries more than refreshOn (#148)#149
garretpremo merged 2 commits into
devfrom
issues/148-refreshon-only-warning

Conversation

@garretpremo

Copy link
Copy Markdown
Contributor

Closes #148

Summary

The session.endpoint warning added in #147 was over-broad. It fired whenever rawSessionAuth existed without session.endpoint — but an endpoint-less sessionAuth: { refreshOn: [401] } block is a configuration #135 deliberately supports, since refreshOn is sourced from rawSessionAuth precisely so it survives the mergedSessionAuth narrowing. A correctly-configured project on that route printed the warning on every single CLI invocation.

The warning's actual target is the typo case — a block carrying handshake keys with no reachable endpoint (sessions: for session:). Gating on "carries some key other than refreshOn" keeps that diagnostic and goes quiet for the intentional config.

What changes

src/auth/refresh-wiring.ts — gate the warning, and say what was found

if (rawSessionAuth && !mergedSessionAuth) {
    const foundKeys = Object.keys(rawSessionAuth).filter(
        key => key !== 'refreshOn' && (rawSessionAuth as Record<string, unknown>)[key] !== undefined,
    );

    if (foundKeys.length > 0) {
        console.warn(
            `[apijack] sessionAuth is set but missing session.endpoint — SessionAuthStrategy will not be used. Found: ${foundKeys.join(', ')}.`,
        );
    }
}

The !== undefined clause is load-bearing, not defensive padding: deepMergeSessionAuth always assigns onChallenge = overrideFn ?? baseFn, so the key is present-but-undefined on every merged block. Without the clause, a refreshOn-only config would still warn.

Naming the found keys is what makes the diagnostic actionable — a sessions: typo now reports Found: sessions, cookies instead of leaving the user to diff their config against the docs.

JSDoc no longer claims purity

The function grew an I/O side effect in #147 while its docstring still read "Kept pure so both sites stay in lockstep". The lockstep rationale stands and is kept; the purity claim is replaced with a note about the diagnostic.

Acceptance criteria

  • A deliberate endpoint-less sessionAuth: { refreshOn: [...] } block produces no warning
  • A block with handshake keys but no reachable session.endpoint (the typo case) still warns
  • The warning names the offending block's top-level keys
  • refreshOn behavior from sessionAuth.refreshOn is unreachable for projects using a custom AuthStrategy #135 is unchanged in both cases
  • The refresh-wiring.ts JSDoc no longer claims purity

Test plan

  • bun test — 1043 pass / 0 fail (up from 1041 on dev)
  • bun run lint — 0 errors (118 pre-existing warnings, unchanged)

New coverage in tests/auth/refresh-wiring.test.ts:

  • refreshOn-only block is silent; typo'd block warns and the message names sessions and cookies but not refreshOn ('sessions' isn't a substring of 'session.endpoint', so that assertion is real rather than incidental)
  • both cases still assert mergedSessionAuth === undefined and refreshOn === [401], so sessionAuth.refreshOn is unreachable for projects using a custom AuthStrategy #135's behavior is pinned in both directions
  • a third test pins the onChallenge case (see below)

Every clause of the new filter was mutation-tested during review — reverting the gate, inverting it, dropping !== undefined, or dropping key !== 'refreshOn' each fails at least one test, and dropping the refreshOn exclusion fails both.

Known behavior, deliberately kept

bin/apijack.ts:130-132 and src/run-routine.ts:140-142 inject onChallenge from .apijack/auth.ts into the sessionAuth object. A project with a refreshOn-only block and a custom onChallenge export therefore still warns, reporting Found: onChallenge — a key the user never wrote into that block. This is left as-is and pinned by a test: onChallenge is consumed only by SessionAuthStrategy, which is never constructed without an endpoint, so in that configuration the hook is genuinely dead and the warning is pointing at a real mistake. Excluding it from foundKeys would discard a legitimate diagnostic.

… refreshOn (#148)

An endpoint-less `sessionAuth: { refreshOn: [...] }` block is a deliberate,
supported config (#135) - refreshOn is sourced from the raw merge precisely
so it survives the mergedSessionAuth narrowing. The warning added in #147
fired for that case too, so a correctly-configured project saw it on every
invocation. Now it only fires when the block carries other keys (the
signature of a typo'd handshake key like `sessions:` for `session:`), and
names the offending keys so the typo is easy to spot.
…sionAuth (#148)

bin/apijack.ts and run-routine.ts inject onChallenge from .apijack/auth.ts into
the sessionAuth object, so a refreshOn-only block with a defined onChallenge
warns and names onChallenge in foundKeys. That's intentional, not a leak of the
bug just fixed: onChallenge is only consumed by SessionAuthStrategy, which is
never constructed without a session.endpoint, so the hook is genuinely dead in
that config and the warning points at a real mistake.
@github-actions github-actions Bot added the needs review Open PR awaiting review label Aug 4, 2026
@garretpremo garretpremo added review in progress Review is actively underway and removed needs review Open PR awaiting review labels Aug 4, 2026

@garretpremo garretpremo left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review by claude — generated by the review-issue skill. Treat as advisory; a human still owns the merge decision.

The gate is correct and every acceptance criterion in #148 is met: the refreshOn-only block goes silent, the typo'd-handshake-key block still warns and now names what it found, refreshOn is pinned in both directions, and the JSDoc purity claim is gone. Scope is tight — two files, no drive-by edits — and all 11 checks are green on 55c0284.

I verified the two load-bearing claims in the description independently:

  • deepMergeSessionAuth really does always assign onChallenge (src/auth/config-merge.ts:24, result.onChallenge = overrideFn ?? baseFn), and structuredClone({ ...base, onChallenge: undefined }) preserves the key with an undefined value — so Object.keys sees onChallenge on every merged block and the !== undefined clause is genuinely required, not padding.
  • The onChallenge injection at bin/apijack.ts and src/run-routine.ts is guarded by if (sessionAuth && projectOnChallenge), so it only lands when a project actually exports the hook. Acceptance criterion 1 therefore holds on the real shared-binary path, not just in the unit test.
Non-blocking observations
  • The Found: onChallenge case is reasoned about well and pinned by a test, but the message a user actually sees names a key they never typed into their config — they wrote refreshOn and a .apijack/auth.ts export, and got told sessionAuth is set but missing session.endpoint ... Found: onChallenge. The diagnostic is pointing at a real dead hook, so keeping it is the right call; the phrasing just doesn't connect the key back to where it came from. A future tweak could special-case it (Found: onChallenge (injected from .apijack/auth.ts)). Not worth blocking on, and not in #148's scope.
  • foundKeys filters on !== undefined only, so a JSON env config with an explicit "cookies": null counts as a found key and gets named. That's arguably correct — an explicit null is still something the user wrote — but it's a slightly different notion of "present" than the undefined case the filter was written for.
  • No caller asserts on the new Found: suffix outside the unit test; tests/cli-builder-refresh-wiring.integration.test.ts covers the wiring but not the message. Fine as-is given the message is a diagnostic rather than API surface.
Nitpicks
  • The message ends at Found: sessions, cookies. without a hint at the fix. For the typo case the found-key list is usually enough to spot sessions vs session, so this is marginal.
  • The description's mutation-testing note is a good signal to have in the PR body — worth keeping that habit, since it's the part of the test suite that's hardest to verify from the diff alone.

@garretpremo garretpremo added first pass reviewed Review passed with no blocking issues and removed review in progress Review is actively underway labels Aug 4, 2026

@garretpremo garretpremo left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Final review by claude — generated by the final-review skill. CI is green, no blockers, soak window elapsed. Marking approved and merging.

Approved.

Outstanding observations from first-pass review

  • The Found: onChallenge case is reasoned about well and pinned by a test, but the message a user actually sees names a key they never typed into their config — they wrote refreshOn and a .apijack/auth.ts export, and got told sessionAuth is set but missing session.endpoint ... Found: onChallenge. The diagnostic is pointing at a real dead hook, so keeping it is the right call; the phrasing just doesn't connect the key back to where it came from. A future tweak could special-case it (Found: onChallenge (injected from .apijack/auth.ts)). Not worth blocking on, and not in #148's scope. (#150)
  • foundKeys filters on !== undefined only, so a JSON env config with an explicit "cookies": null counts as a found key and gets named. That's arguably correct — an explicit null is still something the user wrote — but it's a slightly different notion of "present" than the undefined case the filter was written for. (#150)
  • No caller asserts on the new Found: suffix outside the unit test; tests/cli-builder-refresh-wiring.integration.test.ts covers the wiring but not the message. Fine as-is given the message is a diagnostic rather than API surface.

@garretpremo garretpremo added approved PR has been fully approved and is ready to merge and removed first pass reviewed Review passed with no blocking issues labels Aug 4, 2026
@garretpremo
garretpremo merged commit 40fb9ab into dev Aug 4, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved PR has been fully approved and is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant