release: Claude Code Native 5.0.0 — install screen, native login, session data, compliance - #40
Closed
serialexperimentslainnnn wants to merge 11 commits into
Closed
serialexperimentslainnnn wants to merge 11 commits into
serialexperimentslainnnn wants to merge 11 commits into
Conversation
v5.0.0 shipped with an UNVERIFIED tag. Three things had to be wrong at once, and
all three were:
- gen-ci-signing-key.sh generated the key with Name-Real but no Name-Email, so
its uid carried no address at all. GitHub reported it as `emails=` — empty.
- bootstrap-ci.sh never registered the key on the GitHub ACCOUNT. It certified
the key with the YubiKey, which is a different mechanism: certification makes
`gpg --verify` meaningful to a human, registration is what the "Verified"
badge reads. The two were conflated.
- release.yml tagged as `github-actions[bot]@users.noreply.github.com`, an
address that cannot appear on anyone's key.
GitHub marks a signature verified only when the tagger email, an email on a uid
of a registered key, and a verified account email all agree. Any one of the above
defeats it permanently.
The address is now DERIVED, never written down. gen-ci-signing-key.sh reads it
from the maintainer key via `git config user.signingkey`; release.yml reads it
from the signing key it just imported. Each link derives from the previous one,
so rotating the key is sufficient on its own and nothing can drift out of step.
It also keeps the address out of the repository — this project deliberately
publishes no contact email anywhere (CHANGELOG 5.0.0), and a committed script is
published.
Both now fail loudly where they used to continue: the workflow aborts if the
imported key has no email rather than producing another unverifiable tag, and the
generator aborts rather than minting another unusable key.
Also in bootstrap-ci.sh: it no longer re-adds a required reviewer (publication is
automatic on merge to main, and on a single-collaborator repository the merge
already is the human act), and it adds `main` to the deployment branch policy —
without that entry the job is rejected before the workflow is even reached, which
is how the first attempt failed.
Finally, the GitHub Release now carries CHANGELOG.md instead of RELEASE_NOTES.md.
The latter is the Marketplace copy — emoji-led, second person — and that register
belongs on a storefront where someone is deciding whether to install, not in
front of a person who arrived at a release page because something broke.
Different readers, different documents: build.gradle.kts still feeds the
Marketplace panel from RELEASE_NOTES.md. The extracted section is ~27 KB against
a 125 000-character limit, and an empty extraction now fails the release rather
than publishing blank notes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`./gradlew checkDrift` reports no drift: the versions advanced and the protocol surface is unchanged, so this is a baseline bump and nothing else. `npm audit --omit=dev` stays at zero. NB the binary is a system-wide install here (`/usr/bin/claude`) while checkDrift defaults to `~/.local/bin/claude`, so it needs `-PclaudeBinary=/usr/bin/claude` on this machine. The weekly drift.yml installs the CLI itself and is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A tab opened without the `claude` binary showed a loading screen that faded into an empty tab explained only by a toast. It now shows an install card: one button per OFFICIAL route for the OS (Linux script + apt/dnf/apk by distro detection, macOS script + brew, Windows ps1/winget/cmd), each next to the exact command it runs, copyable — where a network blocks one route the command is the fallback. A typed path accepts a file or a directory and is validated by running `--version` and requiring the answer to name Claude Code. Sign-in is a card rather than a command, and comes BEFORE the loading screen: verifying credentials needs no session, so launching one we already know is unauthenticated only buys a spawned process and a turn that fails for a reason known at click time. The subscription flow runs `auth login` (NOT `setup-token`, whose reduced grant drops scopes Claude Code exercises — file upload, which is what a pasted attachment travels on) under a PTY, driven by the card through a `LoginUi` seam. The binary opens the browser and captures the callback itself, so the code field is an optional fallback on the same screen. The three boot states are now RE-DERIVED every few seconds rather than decided once inside `start()`. That is the fix for a tab opened before the install keeping its stale answer until it was closed and reopened. CREDENTIALS. `auth login` leaves `~/.claude/.credentials.json` in plaintext, readable by anything running as the user and shared with the terminal CLI. CredentialsVault harvests it into the IDE PasswordSafe and deletes it, and NOTHING writes it back — the credential reaches the binary as CLAUDE_CODE_OAUTH_TOKEN, which takes precedence over the binary's own store (verified on 2.1.223: `auth status` flips authMethod claude.ai to oauth_token). Two costs, both accepted rather than hidden: only the binary can spend the refresh token and it does so through that file, so an expired credential counts as signed out and the card returns; and the oauth_token identity is reduced, so the account email is unavailable and the plan is read from the vaulted blob. The vault refuses to run against a real home from a test JVM. That is not caution: the integration tests start a real session, whose launch() harvested the developer's own credentials into a throwaway test safe and deleted them — invisible while the file was still written back, destructive the moment it was not. API keys go to their own per-provider slot, the same mechanism DeepSeek uses, so the card and Settings are one credential and no provider can overwrite another's. ApiKeyApproval fixes a valid key being rejected: the binary demands each key be approved once and records it in ~/.claude.json, and a --print session has nobody to ask — so entering it in the card writes that approval, and the key is verified before being stored at all. Also: utilization is a 0-100 percentage, not a fraction (1% rendered as a full window, reachable by everyone at the start of every reset window); meters poll once per second with no overlapping round-trips, since the control channel is shared with tool traffic and a pile-up made tool cards linger; and everything per-process is re-requested on every launch, not just the first, so the dashboard fills after a sign-out/sign-in without needing a prompt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The session hands the binary its credential through a private CLAUDE_CONFIG_DIR
on tmpfs — memory, wiped at reboot — owner-only, credential 0600, with the real
configuration (session history, settings, skills, account profile) symlinked in
so /resume and everything else behave unchanged.
That is what the dashboard needs: authenticated through an environment token the
binary reports no plan, no account and no rate-limit windows at all. Measured on
2.1.223 against one account, one moment, identical launch arguments:
config dir -> subscription_type "max", rate_limits_available true,
five_hour 11%, seven_day 94%
env token -> subscription_type null, rate_limits_available false,
rate_limits null
Token refresh happens in that directory and the refreshed value returns to the
safe at teardown, so a login stays alive rather than expiring into a card. With
no tmpfs available the env token is used instead — never a credential written
quietly to a real disk.
Also here: the plan bars read each source on the scale it uses (live rate-limit
events carry a 0..1 fraction, the on-demand usage reply 0..100 — captured live
while claude.ai showed 92%); the chat is reachable only while the process is
running and answering, with install / sign-in / loading / chat as one ordered
flow that walks backwards on any loss; the credentials file is absorbed once at
startup rather than on every poll, which is what a sign-in needs to complete
through the browser; and CC-TRACE debug lines record what the binary answered,
what the decode made of it, and what the dashboard was handed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the Anthropic Console route to the sign-in card as a BUTTON beside the subscription one: its OAuth consent carries `org:create_api_key`, so the binary provisions the key for Claude Code itself and an organisation no longer has to distribute a pasted credential. Typing a key by hand becomes the exception and moves behind a disclosure. - `RuntimeConfigDir` builds a per-session owner-only temp dir on every platform. The `/dev/shm` special case bought a shorter-lived file on ONE OS in exchange for the plugin behaving differently on the two most people use; the encrypted safe is the persistent memory, the dir is a disposable projection of it. On a machine with no `~/.claude.json` the account banked at sign-in is written back out, so the binary knows who it is and the dashboard can name the account. - `SecretStore.ACCOUNT_PROFILE` sits OUTSIDE the mutually-exclusive auth modes: learning the user's email must not evict the credential next to it. - `ClaudeSettings.signedOut` makes Log out mean something. With no credential of its own the plugin rides the binary's login (which is what makes it work on macOS), so clearing the safe alone left the next session signed in again. - `ClaudeLoginFlow` no longer writes into or kills the PTY: `auth login` is a one-shot command that exits 0 on its own, so anything we send can only turn a clean success into a failure. It now logs the full transcript, ANSI stripped and tokens masked via `LoginOutputParser.redactSecrets` — this regression left nothing behind but an exit code, which is how it stayed invisible. - `TerminalLauncher.loginCommand` takes the subcommand as argv (`--console`, `--sso`); the terminal tab already got a real argv list, so `loginArgv` goes. 728 JVM tests, 104 frontend, green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tool window held every chat as a content tab, laid out by the platform's `TabContentLayout`, which does not scroll: past a handful of chats it stops drawing the earliest ones and buries them behind a `⌄` popup. The tool window now holds ONE content and every chat is a `TabInfo` in the plugin's own `ChatTabsPanel`, built on `JBTabs` — the editor's widget, whose single-row layout scrolls. The close button is the part that took five versions, so the reason is worth recording: `ActionPanel` — the thing that turns a tab's action group into the little button — builds its buttons through a traverser that filters on `actionUpdateThread == ActionUpdateThread.EDT`, and `AnAction` answers `BGT` by default. An action that does not declare EDT is dropped on the floor and the tab is simply drawn without a close button, at any width, hovered or not. The platform's own editor-tab `CloseTab` declares it; now so does ours. Two more things guard it, both read off the platform rather than guessed: `setTabLabelActionsAutoHide(false)` so it is drawn without hovering, and `setSupportsCompression(false)` because `TabLabelLayout` bounds the EAST component — which IS the action panel — to 0x0 whenever it has to squeeze a label below its preferred width. Middle-click closes too. Sign out moves onto the gear menu (`SignOutAction`), delegating to the one logout sequence in `OnboardingController`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`koverVerify` failed on the PR, not a test: the auth commit added ~200 lines to `ClaudeSession`/`LoginCoordinator` and pushed the `session` package to 64.61%, under its 65% floor. Fixed by covering behaviour that was genuinely untested, not by lowering the gate. - `LoginModeTest` pins the argv of each sign-in route, including through `TerminalLauncher.loginCommand`. The flag IS the route (`--console` bills API usage against an organization), so a mix-up signs the user into the wrong thing while looking like a working login. - `RollbackManagerHeadlessTest` covers what the Diff History panel lists: the join of transcript rows and captured snapshots (a row with no snapshot cannot be reverted, so it must not be offered), a real revert with its read-state reseed, and the project-root refusal — a transcript can name any path, and a rollback must never restore stale contents outside the tree. session package 64.61% -> 65.35%. 737 JVM tests, 104 frontend, green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The class doc still said "in RAM" and "tmpfs (`/dev/shm`)" over code that now builds an ordinary per-session temp dir — the worst kind of comment, the one that does not fail and gets believed. It now states what is actually true and what is NOT claimed: the only persistent copy is the encrypted safe, and what lands in the dir is a plaintext owner-only projection of it for the life of the session. Better than a permanent `~/.claude/.credentials.json`, but not "never touches a disk" — the phrasing that bought the `/dev/shm` special case in the first place. Also drops the `SystemInfo` import left orphaned by removing `isAvailable()`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two defects with one root: the plugin handed the binary only `accessToken`, so the session carried no OAuth scopes. The SDK is explicit — `get_usage` answers `rate_limits_available: false` on a "missing profile scope" — so every plan meter went dark. That was misdiagnosed as "the binary only reports this from its own config directory", and the fix built on that premise was a relocated `CLAUDE_CONFIG_DIR` full of symlinks into `~/.claude`, torn down with `dir.deleteRecursively()`. `FileTreeWalk` follows directory symlinks, so every stop() — a logout, a closed tab, a closed IDE — descended through them and emptied the real directories: projects (every past conversation, in every project), skills, sessions, cache. It destroyed a user's history. - `CredentialsVault.envOverlay` hands over the blob field by field: refresh token, `CLAUDE_CODE_OAUTH_SCOPES`, subscription type, rate-limit tier and the account. Absent fields are omitted, never blanked. `SDK_HAS_OAUTH_REFRESH` is deliberately not claimed — this host cannot spend a refresh token. Verified live: the plan-limit panel fills. - `RuntimeConfigDir` deleted outright. No config dir, no symlinks, no recursion. The binary keeps its own `~/.claude`. - `SessionStore.delete` and the "Delete Previous Session…" menu item removed. Transcripts are the user's; the plugin has no business removing them. - `NoFileDeletionContractTest` pins it at the source: recursive deletion banned outright, single-file deletion only in `CredentialsVault`, and only the credentials file it harvested. - Account card: `auth status` is invoked WITHOUT `--json` (it answers JSON on its own; the unknown flag was a non-zero exit, i.e. no answer at all), every field it emits is modelled, and the reply is banked verbatim in the IDE safe as `CLAUDE_AUTH_STATUS` — but only when it names the account, so an anonymous reply cannot overwrite a good one. Ask first, harvest second: the credentials file is the only thing that can answer who is signed in, so taking custody of it before asking left Email and Organization permanently blank. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The account probe went out with `--json`, a flag `auth status` does not know, so it exited non-zero and the plugin read that as "unknown" — no identity at all. With the flag gone the reply is banked verbatim in the IDE safe, and the order is the one that makes it answerable: ask WHO first, take custody of the credentials file second. Reversed, the only thing that could answer is already gone, which is why the dashboard's Email and Organization rows stayed blank while Plan and Provider filled in from other sources. - The identity question is asked only when somebody IS signed in, and never from a test JVM: it resolves the developer's own home and spawns the binary. - `AuthCli.run` destroys a process that outlives its timeout. Without it the timeout only stopped us waiting, leaving the process and its stream readers alive — a leaked-thread failure attributed to whichever test ran next. - `accountJson` picks the first present candidate through one helper instead of five nested elvis chains (detekt: complexity 19 > 15). - `SessionTranscriptReaderParseTest` covers the restore path the integration suite never reached: prompt shapes, thinking vs text, tool_result attribution and error tagging, the command tag applied from an earlier line, the tail cap dropping an orphan output, corrupt lines, metadata first-wins, and the newest-first session listing. session package 64.05% -> 65.35%. 749 JVM tests, 104 frontend, detekt/spotless/eslint/prettier clean, audit 0, verifyPlugin Compatible IC-251 -> IU-262. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…v5.0.0 feat: onboarding, native sign-in and full session telemetry
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.
Release PR for 5.0.0.
build.gradle.ktsis already at5.0.0, so taggingv5.0.0after this merge satisfiesrelease.yml'sguard(tag reachable frommain+ version match).1. Claude Code install screen
A tab opened without the
claudebinary now shows an install card instead of a loading screen that faded into nothing. One button per official install route for the current OS — Linux install script plus apt/dnf/apk by distro detection, macOS script and Homebrew, Windows PowerShell/winget/cmd — each showing the exact command it runs, copyable, so a network that blocks one route still has a way through. Commands run visibly in the IDE terminal. A manual path accepts a file or an install directory and is validated by running--versionand requiring the answer to name Claude Code. Once the binary appears, by any route, the session starts on its own.The boot state is re-derived every few seconds instead of decided once at startup, so installing the binary — or signing in from somewhere else — takes effect without closing the tab.
2. Native login
Sign-in is a card in the tab, shown before a turn can fail on it, not a slash command to know about. The subscription flow is fully native and requests the full OAuth consent (the reduced
setup-tokengrant drops scopes Claude Code exercises, file upload among them): the browser leg completes on its own and pasting the code is an optional fallback on the same screen. Console sign-in is available as its own mode.The credential is kept in the IDE's password safe — OS keychain / KWallet / DPAPI — rather than in plaintext on disk, and is handed to the binary through the process environment, whole: token, refresh token, scopes, subscription type, rate-limit tier and account. An API key typed into the card goes to the same per-provider slot Settings ▸ Provider uses, so there is one credential and two doors onto it, and it is approved once — which is what fixes a valid key being refused by a
--printsession. Log out stops the session and clears the IDE's own copies, without touching the terminal CLI's login.3. Session data in the UI
get_usage, a control request known since 4.0.1 and never sent, returns the whole picture: five-hour and seven-day windows with reset times, per-model weekly buckets, and the extra-credit balance. Shown as dashboard bars and composer dots (blue < 65%, amber < 85%, red above), announced once per threshold per window. It is what users had to leave the IDE to check.claude auth statusreports.4. Chat tabs
Tabs scroll instead of squeezing, and actually show their close button; long titles truncate with the full title in the tooltip.
5. Performance
javax.swing.Timerwhose initial delay equalled its 60s interval, and the timer retires at the end of a turn — those numbers cannot move while idle, so polling them was pure cost.claude auth statuson every dashboard push, and the account profile is cached in memory on a hot path.6. Compliance and standards refactor
The repository was taken through the standards catalogue domain by domain, and the code changed with it:
npm audit --omit=devis 0, and CI asserts the artifact packages no npm code.:focus-visiblebaseline with aforced-colorsfallback, pinned by frontend tests. The EU Accessibility Act has applied since June 2025.LICENSE,THIRD-PARTY-NOTICES.md,LICENSES/*) ships inside the artifact, and CI fails if it is missing: a permissive licence's notice obligation binds on redistribution.LoginCoordinatorextracted fromClaudeSession, keeping it a delegating orchestrator..gitattributes, and three ADRs: release process, threat model (STRIDE, with prompt injection assumed to succeed rather than detected), and i18n deferred with its triggers.7. Fixes
An initialization-order NPE that could take a tab with it; the
<tool_use_error>wrapper stripped instead of rendered as raw markup; failed tool cards that auto-open once and wrap their text; markdown links whose href is a path opening through the same confirmation gate asjb://;ToolSearchand the current first-party agent tools added to the trust allowlist, so a session that defers tool schemas no longer has its unlocking call land in the untrusted branch.Verification
Every gate
ci.ymlruns, locally on the tip of this branch:test— 749 JVM tests, 0 failures;npm test— 104 frontend testskoverVerify—session65.35% against its 65% floor, raised by covering the restore path rather than by lowering the gatedetekt,spotlessCheck, ESLint, Prettier — cleannpm audit --omit=dev --audit-level=low— 0 vulnerabilitiesverifyPlugin— Compatible against IC-251, IC-252, IU-253, IU-261, IU-262Merge with a merge commit, not a rebase, so the tag points at a commit reachable from
main.🤖 Generated with Claude Code