release: Claude Code Native 5.0.0 — plaintext credentials closed, native login, install screen, session data, compliance - #41
Merged
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 was referenced Aug 6, 2026
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. Plaintext credentials, closed
Claude Code's own sign-in leaves its OAuth credentials in
~/.claude/.credentials.json: plaintext on Linux, readable by every process running as the user, and shared with every terminal session on the machine. A bearer token sitting unencrypted in a well-known path is the kind of thing that turns any unrelated code execution into an account compromise, and it is on disk permanently — including long after the IDE is closed.This release closes it. The credential is moved into the IDE's password safe (OS keychain / KWallet / DPAPI), the plaintext file is overwritten and deleted — a login the user made in their own terminal included — and nothing ever writes it back. It reaches the binary through the process environment instead:
/proc/<pid>/environis owner-only where the file was readable by anything running as the user, and it leaves nothing behind when the process exits. An orphan left by a hard IDE kill is folded back into the safe at the next launch.The whole credential travels, not just the access token — token, refresh token, scopes, subscription type, rate-limit tier and account — so the session is a full identity rather than a degraded one. Credentials are environment only, never argv (
/proc/<pid>/cmdlineis world-readable), and never in logs, the transcript or any XML the plugin writes.An API key gets the same treatment: its own per-provider slot shared with Settings ▸ Provider, so the card and the settings field are one credential rather than two that disagree, and no provider's key can overwrite another's.
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; pasting the code is an optional fallback on the same screen. Console sign-in is available as its own mode.claude auth statusverifies whichever identity is effective, and Log out stops the session and clears the IDE's copies without touching the terminal CLI's login.3. Claude Code install screen
A tab opened without the
claudebinary shows an install card instead of a loading screen that faded into nothing: one button per official 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 a 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 rather than decided once at startup, so installing the binary or signing in elsewhere no longer requires closing the tab.4. 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. 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.auth statusreports.5. Chat tabs
Tabs scroll instead of squeezing and actually show their close button; long titles truncate with the full title in the tooltip.
6. Performance
Context and cost are polled when the session becomes ready, when the tab is opened and at both edges of a turn, instead of waiting out a
javax.swing.Timerwhose initial delay equalled its 60s interval — so the figures appear when they change rather than up to a minute later. The timer also retires at the end of a turn: those numbers cannot move while the session is idle.7. 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, prompt injection assumed to succeed rather than detected), and i18n deferred with its triggers.8. Fixes
An initialization-order NPE that could take a tab with it; the CLI's
<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