Skip to content

fix(auth): let auth login replace a token the service rejects - #467

Merged
theCodeDrift merged 3 commits into
mainfrom
worktree-450---can-we
Oct 6, 2026
Merged

theCodeDrift merged 3 commits into
mainfrom
worktree-450---can-we

Conversation

@theCodeDrift

Copy link
Copy Markdown
Member

Every message that asks a user to re-authenticate after a 401 pointed at auth login, and auth login refused whenever any token was present. A token the service has revoked or expired is still present locally, so the advice could not be followed. With TASKLESS_TOKEN set it was worse: auth logout printed Not logged in., auth login kept refusing, and nothing mentioned the variable.

What changes

  • auth login checks a saved token with the service before refusing. A 401 runs the device flow and replaces the token. An accepted token, or a service that could not answer, is kept with the existing "already logged in" answer, since an unreachable service says nothing about the token. --json output is unchanged.
  • One remedy for every 401 (rejectedTokenRemedy() in auth/token.ts), used by check, rule create/improve (submit, poll and fetch) and rule restore/rollback/revisions. A saved token gets auth login. A TASKLESS_TOKEN token gets "replace or unset it; auth login and auth logout do not change it". The rule create/improve messages that only said Log in again. now name the command.
  • TASKLESS_TOKEN is named:
    • auth login points at the variable instead of at auth logout.
    • auth logout says the variable is still used.
    • taskless auth adds "via TASKLESS_TOKEN" and separates a rejected token, with its remedy, from an unreachable service.
  • The org-not-found hint says auth logout then auth login. Its token is valid, so auth login alone would keep it.
  • auth agent recipe topic v2, and a patch changeset.

OpenSpec

rejected-token-recovery, a single PR, archived in the second commit. It adds the login, logout and 401 behavior to cli-auth. It also corrects requirements that still described the global ~/.config/taskless/auth.json store, which the CLI no longer reads or writes. Two of those are REMOVED and re-ADDED under new titles, because a MODIFIED block can neither rename a requirement nor drop a scenario. The proposal lists each scenario that was replaced. I compared the scenario list before and after the archive: every earlier scenario is either still present or listed there.

Verification

pnpm typecheck, pnpm lint, openspec validate --specs --strict, and the full CLI suite (2035 tests) pass after rebasing onto 416a2db. New tests cover:

  • loginInteractive with an environment token, an accepted token, an unreachable service, and a rejected token
  • the remedy text with and without the variable
  • the built CLI's auth login/auth logout with TASKLESS_TOKEN set

Fixes #450

@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Claude finished @theCodeDrift's task in 3m 37s —— View job


Review complete

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contained no prior review threads, reviews, or inline comments for this PR (only the triggering @claude /review comment itself), so this is the first review of this PR — the full diff was assessed from scratch.

  • Read prior review data
  • Fetch PR diff and metadata
  • Read changed files in full where needed
  • Review for correctness, security, performance, tests, clarity
  • Post inline comments for concrete issues
  • Post final summary

Summary

Solid, well-scoped change. The core behavior change (auth login probing a saved token with the service before refusing, replacing it only on a definite 401, and keeping it when the service is unreachable) is implemented correctly in login-interactive.ts, matches the spec's "An unreachable service keeps the saved token" scenario, and is well covered by the new tests. The rejectedTokenRemedy() consolidation in auth/token.ts is applied consistently across every unauthorized branch I could find (rules.ts, generate.ts ×2, plan-check.ts, recover.ts's shared failure() used by restore/rollback/revisions), so the "every 401 names a fix" claim in the PR body checks out against the code. The OpenSpec archive was also done carefully — the two renamed requirements correctly use REMOVED+ADDED rather than MODIFIED (per this repo's spec-delta rule that a MODIFIED block can't rename a requirement or drop a scenario), and I spot-checked that the kept requirement titles ("Auth login initiates Device Flow", "Auth logout removes saved token", "Token file format", "Token resolution is a shared utility") are byte-identical between the delta and the resulting openspec/specs/cli-auth/spec.md, with no scenarios silently dropped.

One gap worth addressing:

  • [New] No test exercises the new branches of bare taskless auth (status). commands/auth.ts (lines 213–235) now has three distinct status outcomes — ok with a " via TASKLESS_TOKEN" suffix, unauthorized reporting "the token was rejected" with rejectedTokenRemedy(), and unavailable reporting "unable to verify identity" — none of which have any test, unit or e2e. This is exactly the behavior the new "A rejected token's message names a fix that works" requirement adds two scenarios for ("Status reports a rejected token with its remedy" and "Status does not call an unverified token invalid"), and the PR's own Verification section only mentions testing auth login/auth logout with TASKLESS_TOKEN, not status. The pre-existing cli.test.ts only covers the "Not logged in." path. Since authCommand's run isn't unit-testable in isolation today (no test imports it directly — only built-CLI e2e), the practical fix is either a built-CLI test with a stubbed/mocked service response, or extracting the status-outcome formatting into a small pure function (similar to rejectedTokenRemedy) that can be unit tested directly.

Minor, non-blocking observation:

  • commands/info.ts and auth/org.ts still use the older fetchWhoami (auth/whoami.ts), which swallows every network/HTTP failure into undefined, while this PR adds a second, richer whoami consumer (api/v2.ts) that distinguishes unauthorized from unavailable in commands/auth.ts and login-interactive.ts. Not a bug and out of this PR's stated scope, but it leaves two whoami call paths with different failure granularity in the same codebase.

@theCodeDrift
theCodeDrift force-pushed the worktree-450---can-we branch from 5426900 to 3d628d4 Compare October 6, 2026 05:10
@theCodeDrift

Copy link
Copy Markdown
Member Author

Re: @claude[bot] — "Claude finished @theCodeDrift's task in 3m 37s…"
#467 (comment)

Status coverage: fixed in 3d628d4. The status wording moved into a pure describeAuthStatus() (auth/status.ts), and test/auth-status.test.ts covers an accepted token with and without TASKLESS_TOKEN, a rejected token from each source, and an unreachable service (which must not call the token invalid). The fetchWhoami split in info/org is left as is: those callers only need "identity or not", and folding them in is outside this fix.

Also rebased onto main, which had restructured check's unverified notices into a cause plus remedy sentences. rejectedTokenRemedy() now returns a full sentence so it slots into that list, and the check 401 notice names TASKLESS_TOKEN when that is the token's source.

— AI Coding Agent

@theCodeDrift
theCodeDrift merged commit 6a9ceb5 into main Oct 6, 2026
4 checks passed
@theCodeDrift
theCodeDrift deleted the worktree-450---can-we branch October 6, 2026 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A rejected token can't be fixed with auth login, which every 401 message recommends

1 participant