Skip to content

release: Claude Code Native 5.0.0 — install screen, native login, session data, compliance - #40

Closed
serialexperimentslainnnn wants to merge 11 commits into
mainfrom
develop
Closed

serialexperimentslainnnn wants to merge 11 commits into
mainfrom
develop

Conversation

@serialexperimentslainnnn

@serialexperimentslainnnn serialexperimentslainnnn commented Aug 6, 2026 •

Copy link
Copy Markdown
Owner

Release PR for 5.0.0. build.gradle.kts is already at 5.0.0, so tagging v5.0.0 after this merge satisfies release.yml's guard (tag reachable from main + version match).

1. Claude Code install screen

A tab opened without the claude binary 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 --version and 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-token grant 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 --print session. 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

  • Plan limits panel — 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.
  • Account card — email, organization, plan and provider, every field claude auth status reports.
  • Context and cost — polled on ready, on tab open and at both turn edges.

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

  • Context and cost no longer wait out a javax.swing.Timer whose 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.
  • The account identity is read from the safe instead of spawning claude auth status on every dashboard push, and the account profile is cached in memory on a hot path.
  • Per-write VFS refresh for the exact paths touched, rather than a project-tree refresh per turn.

6. Compliance and standards refactor

The repository was taken through the standards catalogue domain by domain, and the code changed with it:

  • Dependencies — the protocol-reference SDK moved out of the distributed scope; npm audit --omit=dev is 0, and CI asserts the artifact packages no npm code.
  • Accessibility — a WCAG 4.1.3 live region and a :focus-visible baseline with a forced-colors fallback, pinned by frontend tests. The EU Accessibility Act has applied since June 2025.
  • Licensing — third-party attribution (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.
  • Architecture — LoginCoordinator extracted from ClaudeSession, keeping it a delegating orchestrator.
  • Governance — commitlint with a versioned hook that degrades to advisory rather than becoming a reason to skip it, .gitattributes, and three ADRs: release process, threat model (STRIDE, with prompt injection assumed to succeed rather than detected), and i18n deferred with its triggers.
  • Test contracts — source-level tests for the two defect classes the compiler cannot see: initialization order in class bodies, and the filesystem-deletion surface (recursive deletion banned; single-file deletion confined to the one plaintext credential the plugin takes into the safe — transcripts and session history are the user's and nothing removes them).

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 as jb://; ToolSearch and 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.yml runs, locally on the tip of this branch:

  • test — 749 JVM tests, 0 failures; npm test — 104 frontend tests
  • koverVerify — session 65.35% against its 65% floor, raised by covering the restore path rather than by lowering the gate
  • detekt, spotlessCheck, ESLint, Prettier — clean
  • npm audit --omit=dev --audit-level=low — 0 vulnerabilities
  • verifyPlugin — Compatible against IC-251, IC-252, IU-253, IU-261, IU-262
  • Smoke-tested by hand in the IDE: install and sign-in cards, plan-limit bars, account card, tool cards, markdown, tables and syntax highlighting.

Merge with a merge commit, not a rebase, so the tag points at a commit reachable from main.

🤖 Generated with Claude Code

serialexperimentslainnnn and others added 11 commits August 6, 2026 07:12
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
@serialexperimentslainnnn serialexperimentslainnnn changed the title release: Claude Code Native 5.0.0 release: Claude Code Native 5.0.0 — install screen, native login, session data, compliance Aug 6, 2026
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.

1 participant