feat(auth): collapse and restyle entire auth status - #2512
Open
gtrrz-victor wants to merge 17 commits into
Open
gtrrz-victor wants to merge 17 commits into
gtrrz-victor wants to merge 17 commits into
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Address the three moderate JSON and single-session count handling issues in auth.go.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Redesigns entire auth status with compact output, session identification, optional listings, and JSON support.
Changes:
- Adds signed relative-duration and qualified-handle formatting.
- Identifies the current session via JWT
fid. - Adds
--sessionsand--jsonmodes. - Updates documentation and related tests.
File summaries
| File | Summary |
|---|---|
cmd/entire/cli/status.go |
Adds signed, bucketed duration formatting. |
cmd/entire/cli/status_test.go |
Tests duration formatting. |
cmd/entire/cli/resolveref.go |
Adds qualified-handle formatting. |
cmd/entire/cli/resolveref_test.go |
Tests handle formatting. |
cmd/entire/cli/auth/session_family_test.go |
Tests JWT family-ID extraction. |
cmd/entire/cli/auth/cell_data_api.go |
Extracts JWT fid claims. |
cmd/entire/cli/auth.go |
Implements auth-status rendering and JSON output. |
CLAUDE.md |
Documents updated auth-status behavior. |
Review details
Suppressed comments (1)
cmd/entire/cli/auth.go:768
- This suppresses the count for every one-session result, even when
currentSessionIndexis-1(for example, an older core with nofid, an opaque token, or an unmatched claim). In that case the verdict has no expiry and no marker, so hiding the only session count makes the output omit the fact that a session exists. Only drop this row when the sole session was actually identified (or pass the current index into this helper and gate on it).
if len(sessions) == 1 {
return explainRow{}, false
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
gtrrz-victor
added a commit
that referenced
this pull request
Sep 17, 2026
Three count-ish fields used omitempty on plain values, so a real zero and a value we never computed encoded identically. - `sessions` was dropped when `--sessions` returned an empty listing, making a satisfied full-list request indistinguishable from the collapsed default, where the key is genuinely absent. It now emits an explicit []. - `available_contexts` was dropped at zero while its `active_sessions` sibling was already a pointer. Zero was doubly ambiguous: ENTIRE_TOKEN mode never reads contexts.json (resolveEnvTokenStatusTarget leaves the count at 0), so absent-because-uncounted and genuinely-none looked the same. It is now set only when the count was actually taken. Both become pointers, matching `active_sessions`, so the rule across the envelope is one thing: absent means "not known", zero means zero. Reported by Copilot on #2512. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2Q8TV28XAYQXH23TGP5GTDC
gtrrz-victor
added a commit
that referenced
this pull request
Sep 17, 2026
Three count-ish fields used omitempty on plain values, so a real zero and a value we never computed encoded identically. - `sessions` was dropped when `--sessions` returned an empty listing, making a satisfied full-list request indistinguishable from the collapsed default, where the key is genuinely absent. It now emits an explicit []. - `available_contexts` was dropped at zero while its `active_sessions` sibling was already a pointer. Zero was doubly ambiguous: ENTIRE_TOKEN mode never reads contexts.json (resolveEnvTokenStatusTarget leaves the count at 0), so absent-because-uncounted and genuinely-none looked the same. It is now set only when the count was actually taken. Both become pointers, matching `active_sessions`, so the rule across the envelope is one thing: absent means "not known", zero means zero. Reported by Copilot on #2512. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2Q8TV28XAYQXH23TGP5GTDC
gtrrz-victor
force-pushed
the
refactor-entire-status
branch
2 times, most recently
from
September 17, 2026 09:26
832e2f6 to
efb0b2b
Compare
gtrrz-victor
added a commit
that referenced
this pull request
Sep 17, 2026
Four findings from the trail review on #2512. **Every relative-time unit band now starts at 1.** humanizeDuration ran days to 60, so the first reachable month was "2mo" and a value ticking past the boundary read 59d then 2mo — as if it had doubled. Days now stop at 30, which costs the precise "in 30d" for a month-long session expiry and buys back a continuous ladder: 1m, 1h, 1d, 1mo. This reaches every timeAgo caller (status, sessions, search, trail list), not just auth. **`logout --everywhere` is offered only alongside the table.** It ends every session at once, and the collapsed view turned those sessions into a count the reader cannot inspect — on a real login, 3 of 4 were browser Console logins invisible there. Before the redesign the table always rendered, so the destructive hint arrived with its subject attached; now the hint waits for `--sessions`. The count row already says how to get there. **One spelling for the login server.** The logged-out and invalid-token lines printed the full URL while every other path printed the bare host. Both now go through authServerHost. **The JSON contract doc contradicted itself**, and the code was the correct half: it claimed `--json` "always carries" the counts, then listed the cases where it does not. The accurate claim is narrower — the drop-at-one collapse is text-only, and JSON omits only what it could not determine, which is what the pointers encode. The `sessions` claim was wrong the same way: it is emitted whenever `--sessions` reached the listing, and the paths that return before it (not logged in, env token, failed fetch) omit it with everything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2QCPCZM6056W23XED33EFT7
`auth status` buried its own answer. A 23-row session table took over the
screen, no row said which session was this CLI's — the one `entire logout`
would end — and every timestamp was a bare YYYY-MM-DD, so three consecutive
rows read 2026-09-16 with no sense of recency. It was also the one command
rendering outside the shared `statusStyles` helpers, with a hand-rolled
`%-13s` label column.
The default view is now the verdict plus an aligned block, ~32 lines down to 9:
● Logged in · expires in 29d
user github:gtrrz-victor
jurisdiction us
context us.auth.entire.io
token macOS Keychain
Identifying the caller's own session is what makes that expiry possible, and
it needs nothing new server-side: a login session IS an OAuth refresh-token
family, and the login JWT already carries the family id as its `fid` claim.
`SessionFamilyIDFromLoginJWT` reads it the way `HomeJurisdictionFromLoginJWT`
reads its own, and the match marks the row `(current)` under `--sessions`.
It fails closed. An absent, unreadable, or unmatched claim renders neither the
marker nor the expiry rather than borrowing another session's, because
everything reachable from here (`logout`, `logout --everywhere`) ends a
session, and ending someone else's is worse than saying nothing.
Also here:
- `--sessions` prints the full table; `--json` was the last rendering verb in
the CLI without one. Both views derive from one resolved value
(`resolveAuthStatus`) so they cannot drift.
- `user` is the provider-qualified handle, the spelling `entire grant` accepts
and `grant … list` prints — so status hands you a value usable in the next
command. Display name, email and the numeric provider id go.
- Count rows for sessions and contexts, dropped at exactly one: the sole
session and sole context are already described above them. Zero sessions
still reports, being a contradiction worth seeing. Text-only — `--json`
always carries the counts, since omitting one from a machine reader loses
information rather than saving a line.
- `formatRelativeDuration` becomes signed and gains a month bucket, so one
ladder serves both "3h ago" and "in 30d". The sign test must precede the
near-zero test, or a future duration reports "just now". This changes
`entire status` for sessions older than 60 days (68d ago → 2mo ago).
- `formatQualifiedHandle` lands beside `parseQualifiedHandle` so the grantee
spelling the CLI prints and the one it accepts cannot drift.
`formatAuthDate` is deleted: nothing renders an absolute auth date any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2Q7S55TXBA9A73STV776QR4
Three count-ish fields used omitempty on plain values, so a real zero and a value we never computed encoded identically. - `sessions` was dropped when `--sessions` returned an empty listing, making a satisfied full-list request indistinguishable from the collapsed default, where the key is genuinely absent. It now emits an explicit []. - `available_contexts` was dropped at zero while its `active_sessions` sibling was already a pointer. Zero was doubly ambiguous: ENTIRE_TOKEN mode never reads contexts.json (resolveEnvTokenStatusTarget leaves the count at 0), so absent-because-uncounted and genuinely-none looked the same. It is now set only when the count was actually taken. Both become pointers, matching `active_sessions`, so the rule across the envelope is one thing: absent means "not known", zero means zero. Reported by Copilot on #2512. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2Q8TV28XAYQXH23TGP5GTDC
The note existed for two reasons, and the stronger one no longer applies.
It explained why the server host and the jurisdiction slug name different
regions, and — mostly — why the display name and email were silently missing,
since a core outside your home region withholds /me's regional block for an
account it does not host.
This view stopped rendering display name and email, so nothing goes missing
for it to explain. What was left restated the two rows it sat between:
jurisdiction us
note served by eu.auth.entire.io, outside your home region
context eu.auth.entire.io
The condition is still real server state (/me's regionalUnavailable block), so
it keeps reaching machine readers as the JSON `foreign_region` flag; it just no
longer spends a row in a block that is meant to be scanned.
authProfileRows loses its styles and target arguments with it, and
api.OriginOnly now has no caller in auth.go.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four findings from the trail review on #2512. **Every relative-time unit band now starts at 1.** humanizeDuration ran days to 60, so the first reachable month was "2mo" and a value ticking past the boundary read 59d then 2mo — as if it had doubled. Days now stop at 30, which costs the precise "in 30d" for a month-long session expiry and buys back a continuous ladder: 1m, 1h, 1d, 1mo. This reaches every timeAgo caller (status, sessions, search, trail list), not just auth. **`logout --everywhere` is offered only alongside the table.** It ends every session at once, and the collapsed view turned those sessions into a count the reader cannot inspect — on a real login, 3 of 4 were browser Console logins invisible there. Before the redesign the table always rendered, so the destructive hint arrived with its subject attached; now the hint waits for `--sessions`. The count row already says how to get there. **One spelling for the login server.** The logged-out and invalid-token lines printed the full URL while every other path printed the bare host. Both now go through authServerHost. **The JSON contract doc contradicted itself**, and the code was the correct half: it claimed `--json` "always carries" the counts, then listed the cases where it does not. The accurate claim is narrower — the drop-at-one collapse is text-only, and JSON omits only what it could not determine, which is what the pointers encode. The `sessions` claim was wrong the same way: it is emitted whenever `--sessions` reached the listing, and the paths that return before it (not logged in, env token, failed fetch) omit it with everything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2QCPCZM6056W23XED33EFT7
`!strings.Contains(got, "note")` matched four letters anywhere in the rendered block, so a context named "notebook", a token-source description, or any future label carrying them would answer for a row that is not there. The flagged assertion now goes through the row builder and the existing hasLabel. Three others I wrote had the same shape with no helper that fits — they only have rendered output, and hasMetadataRow needs a value an absence assertion does not have — so this adds hasMetadataLabel, its value-agnostic sibling, and routes the drop-at-one, env-token and no-jurisdiction checks through it. Substring matching stays where it is correct: "outside your home region", "console.entire.io", "#/profile", "keychain" and "Active Sessions" are content and headings rather than labels, and the comments say so, since telling the two apart is the whole point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M2QEZ6XTHTHE3FA59VCN504A
gtrrz-victor
force-pushed
the
refactor-entire-status
branch
from
September 17, 2026 11:25
b4b4522 to
cdb402f
Compare
The drop-at-one rule rests on the verdict line's `expires` standing in for the
single session, and nothing checked that premise. The expiry is printed only
when the caller's own session was identified, so a login whose `fid` matches no
listed session lost the row anyway and the default view fell to:
● Logged in
user github:alice
context a · eu.auth.entire.io
token …
Run 'entire logout' to end this session.
No count, no expiry, no route to --sessions — and the one session listed is the
login that replaced yours, which is precisely the one worth looking at.
Reachable whenever a family is revoked inside its access token's own lifetime:
resolveStatusTarget falls back to the stale bearer (its comment already names
"revoked family" as a refresh-failure cause), /me still honours it, and fid
names a family the listing no longer holds. Observed on a live account when a
concurrent session logged out everywhere and back in.
TestRunAuthStatus_CountRowsAreDroppedAtOne was passing for the wrong reason: it
used an opaque token, so current was already -1 and it asserted the drop in
exactly the case that must now suppress it. It carries a matching JWT now, and
TestRunAuthStatus_UnidentifiedSingleSessionStillCounts pins the other half.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2QPWDGPX6N30CFDCXG9Q11R
A revoked session does not stop working immediately. The family dies but the
access token it minted keeps being honoured for its own lifetime, so `auth
status` printed a cheerful "Logged in" for a login that could not be renewed
and would lapse without warning:
● Logged in
…
active sessions 0
Zero active sessions while authenticated is the contradiction, and it is now
named rather than left to be inferred:
● Logged in · expires in 6h
! this login was ended elsewhere and cannot be renewed · run 'entire login'
The verdict line deliberately switches source here. Normally `expires` is the
session family's, weeks out; with nothing left to renew the bearer, its own
expiry is when the user is logged out, so that is the honest number to show.
JSON gets login_revoked and token_expires_at, while expires_at stays absent —
no session was attributed, and the two must not be conflated.
What counts as evidence depends on the listing. Zero sessions settles it alone:
the endpoint includes the caller's own session — that is how a matched fid
finds itself — so none listed means none exist, and no truncation explains
zero. With sessions listed, absence is the only evidence, so a fid must have
named something; a core too old to mint one is evidence of nothing and stays
quiet.
An earlier revision also required the refresh to have failed, as corroboration
against a truncated listing. That was wrong: refresh failure is only observable
when a refresh is attempted and fails, and a token far from expiry is returned
without contacting the server at all — so the notice stayed silent in the
commonest case, every session revoked while the bearer still had hours to run.
Verified against a live account in exactly that state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2QRP9B6J41Z1MHC2KY1V5GG
With a session listed, a revoked login printed the notice and then immediately
contradicted it:
! this login was ended elsewhere and cannot be renewed · run 'entire login'
…
Run 'entire logout' to end this session.
There is no session of the caller's left to end — that is what the notice says
— and the session that IS listed belongs to the login that replaced this one,
so offering to end it would be wrong a second way. The banner's `entire login`
is the only action that applies, so the hint is withheld entirely once revoked
is established.
Invisible in the state this was verified against: the hint only prints when at
least one session is listed, and the live account had none.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2QSHXZ3GPG1KK1PDY2MQXHB
main's logout sweep landed while this branch was rewriting `auth status`, and the two met in the status view's logout hint. `entire logout` no longer ends just the caller's session — it ends every CLI session on every saved login and removes them locally, with `--everywhere` adding browser and web. The hint now says so: "end every CLI session" rather than "end this session". The revoked-login gate stays. Its wording leaned on the contradiction between "end this session" and the notice above it; the reason that survives the rename is the one that was always doing the work — the sessions still listed belong to the login that replaced this one, so there is nothing here worth ending, and the banner's `entire login` is the action. docs/development/cli-conventions.md keeps both accounts: this branch's `auth status` collapse and main's `logout` sweep, with the branch's stale description of `logout`'s old flags dropped in favour of main's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M31MA2GZ44A3GV50SN2R055V
Both context rows are a way of saying "this login, not the others", so both now wait until there are others. With a sole saved login there is nothing to distinguish it from: naming it describes a choice the user does not have, and the count beneath it counts the only thing there is. The count row already stopped at one, on the grounds that the row above named it. That reasoning kept the wrong row — the pair is only worth two lines when there is a selection to describe, and worth none when there is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M322T1G78GJZNSVH7MGE1W99
One conflict, in resolveref.go: main's repo-resolution rework inserted resolveProjectRefResolved at the same point this branch inserted formatQualifiedHandle — the line after parseQualifiedHandle. Both are wanted and neither touches the other, so both stay, with formatQualifiedHandle still adjacent to the parser it inverts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M36Z0P8XTTE7HZ4KWJCA00G5
Thirteen findings, most of them about the verdict line making claims the data behind it does not support. The deadline printed beside "Logged in" is now tense-checked: a lapsed instant reads `expired` rather than `expires 19h ago`, which contradicted the word next to it, and an unreadable one is dropped instead of echoed mid-sentence. Because that deadline is the whole premise of dropping the sole-session row, the drop is gated on it having actually rendered — `expires_at` is a plain string with no omitempty, so a session can arrive without one and leave the default view carrying no session information at all. The login server is held to the same test as the context rows: the host joins the `context` row only when the saved logins are spread across more than one server, since logins sharing a server are told apart by their names and the host they share names none of them. `auth status` takes no positional arguments; `auth status sessions` was being read as the collapsed default with the word dropped. It now has command-level tests, so renaming a flag fails something. `--json` keeps its promise of a parseable object: a hard /me failure prints an envelope carrying `error` instead of empty stdout, and `env_token`/`token_source` survive a bearer /me rejected — a script told only `logged_in:false` could not otherwise see that ENTIRE_TOKEN supplied the token and is still winning over every stored context. An account /me returns without a handle falls back to its provider user id, rather than being described by nothing at all. The logout hint names `--everywhere` as a flag instead of sizing it with a count of one server's rows, when logout sweeps every login server. The two hand-rolled JWT claim readers become one `decodeLoginJWTClaims`, gated by tokens.ParseClaims. That drops a duplicated shape check and three duplicated error strings, and settles their disagreement over alg:none on the policy the package already applies to ENTIRE_TOKEN: refuse it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M374VAS6SQV0YK6AGSD2PNN0
The lapsed-deadline finding named two sites and the first fix reached only one. The session table's EXPIRES cell still rendered a dead session as "19h ago", which is the weaker case but the sharper defect: the column heading supplies the verb, so the cell states its tense by implication alone and a reader scanning for a dead session will not pick it up. CREATED and LAST USED keep the plain relative formatter, the past being the tense they report. An unreadable value still reaches the cell verbatim — a column of its own is where a server value this code cannot parse belongs, which is the distinction the prose line draws by dropping it instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M378AJKYJ1C17RF4DS4EK3B5
Reporting where a bearer came from had been hoisted above the not-logged-in return so that a rejected ENTIRE_TOKEN stays visible. That went one step too far: with no token at all, the envelope still named the keychain, telling a script a token is filed there when none is. Gated on a bearer actually existing, which is the condition that was meant all along. Also restores authProfileRows' doc comment, which the new authIdentityLabel had been inserted underneath — leaving the comment attached to the wrong function and authProfileRows with none — and says on HomeJurisdictionFromLoginJWT that it now refuses alg:none, since that is a behaviour change to a helper git-remote-entire shares. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M37AETSE8YRWHMYWFQ6Z5FJ0
The previous version carried a comment excusing its error branches as compiler ceremony. That was half wrong and wholly the wrong fix: only the base64 decode is unreachable once ParseClaims has passed, while the unmarshal is genuinely reachable — ParseClaims type-checks its own fields only, so a claim of the wrong JSON type (a numeric fid) sails through it and fails here. The two now share one error path, which removes the unreachable branch without pretending the reachable one beside it was ceremony too, and the comment states what the structure is rather than apologising for it. TestSessionFamilyIDFromLoginJWT pins the wrong-typed claim, which nothing covered before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M37B2AC09PDZ7AQCG1T63B60
…ones The envelope was guaranteed inside runAuthStatus while the two failures raised before it — an unresolvable target (malformed ENTIRE_TOKEN, unreadable contexts.json, unknown --context) and the TLS refusal on an http:// login server — still returned bare errors. `auth status --json | jq .logged_in` failed to parse on exactly the inputs a script is least able to anticipate. A promise that holds only past a certain line in the command is not one a caller can use. All three now route through authStatusJSONFailure. Writing the tests turned up the other half: cobra prints its usage dump to stdout, so on --json it would follow the envelope there and leave nothing parseable. The root command silences usage, but the contract should not depend on where this command is mounted, so RunE silences it itself — argument validation has already run by then, which is the only error a usage dump helps with, and `auth status sessions` still gets one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M37BN196R04KZEYVPC6021RD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://entire.io/gh/entireio/cli/trails/1359
Why
entire auth statusburied its own answer:Three problems. The table dwarfed the answer. No row said which session was
this CLI's — the one
entire logoutwould end. And every timestamp was abare
YYYY-MM-DD, so three consecutive rows read2026-09-16with no sense ofrecency.
It was also the one command rendering outside the shared
statusStyleshelpers, with a hand-rolled
%-13slabel column, and the last rendering verbin the CLI with no
--json.What it looks like now
~32 lines down to 9.
--sessionsprints the full table with the caller's rowmarked:
Identifying the caller's own session
This is the one new capability, and it needs nothing new server-side. A
login session is an OAuth refresh-token family (already documented at
api/auth_sessions.go:12), and the login JWT already carries the family id asits
fidclaim.SessionFamilyIDFromLoginJWTreads it exactly the wayHomeJurisdictionFromLoginJWTreads its own — unverified, because it onlyselects a row out of a listing the server itself authorized.
It fails closed. An absent, unreadable, or unmatched claim renders neither
the marker nor the expiry rather than borrowing another session's. Everything
reachable from this screen (
logout,logout --everywhere) ends a session,and ending someone else's is worse than saying nothing.
Verified against the live API before building on it: the claim matches, and
current_session_idin--jsonround-trips to the marked row.Also here
--json, with both views derived from one resolved value(
resolveAuthStatus) so they cannot drift. Timestamps stay RFC3339 there —the humanised form is a reading aid, not something to parse.
active_sessionsis a pointer, so a failed listing is absent rather thanreported as zero: "we could not ask" and "you have none" are different
answers.
useris the provider-qualified handle (github:alice) — the spellingentire grant …accepts andgrant … listprints, so status now hands you avalue usable in the next command. Display name, email and the numeric
provider id are dropped.
already described by the verdict line and the
contextrow. Zero sessionsstill reports, being a contradiction worth seeing. Text-only:
--jsonalwayscarries the counts.
formatRelativeDurationis now signed with a month bucket, so one ladderserves
3h agoandin 30d.entire statusfor sessionsolder than 60 days (
68d ago→2mo ago) — deliberate, to keep one ladder.formatQualifiedHandlelands besideparseQualifiedHandleso thegrantee spelling the CLI prints and the one it accepts cannot drift.
formatAuthDatedeleted — nothing renders an absolute auth date any more.Review notes
ForeignRegionnote's comment justified it partly as explaining absentdisplay name/email. Those are gone from this view, and handle/provider/
jurisdiction all come from
/me's global and auth blocks (neverregional, the only block a foreign core drops), so nothing can go missingnow. The note is retargeted to the region split alone.
authProfile.DisplayName/Email/ProviderUserIDbecome unread herebut are still read by
setup_identity.go, so they stay.--jsondeliberately mirrors the text collapse rather than always dumpingthe array, so
--sessionskeeps its meaning in both modes.Testing
mise run fmt && mise run lintclean; fullmise run test:cigreen (unit,integration, Vogon e2e canary).
New coverage: signed/bucketed duration ladder incl. the 60-day boundary and
both sides of zero;
fidclaim read (present, absent, malformed); the markerlanding on the right row and on no row when unmatched; count rows at 0/1/many;
context row with name==host, name!=host, and env-token mode; the
--jsonenvelope decoded back into its struct.
Manually verified against a real login: default view,
--sessions,--json,--json --sessions, piped (no ANSI, columns still aligned),NO_COLOR=1, andentire statusto confirm the shared time helper didn't regress it.Note: the branch name says "entire status" but the work is entirely
entire auth status— happy to rename.🤖 Generated with Claude Code
Note
Medium Risk
Changes auth/session UX and script-facing
--jsonoutput plus unverified JWTfidmatching for logout hints, but does not alter server auth; the shared relative-time helper also shifts howentire statusformats very old sessions.Overview
entire auth statusis redesigned: a short verdict line (with relative expiry when the current session is known), aligned metadata rows via sharedstatusStyles, and no default session table. Use--sessionsfor the full table;--json/--json --sessionsfor scripting, both built from oneresolveAuthStatusresult so text and JSON stay aligned.The caller’s session is picked by matching the login JWT
fidclaim to listed session ids (SessionFamilyIDFromLoginJWT); unmatched claims show no(current)marker and no borrowed expiry. Output emphasizes a provider-qualifieduser(github:alice), drops display name/email from this view, adds active sessions / available contexts count rows (hidden when the count is 1 in text only), and uses relative session timestamps instead offormatAuthDate.formatRelativeDurationnow handles future times (in 30d) and a month bucket, shared withentire status(sessions older than ~60 days read as months). Docs inCLAUDE.mddescribe the new flags and collapse rules.Reviewed by Cursor Bugbot for commit 1b4daad. Configure here.