* Send the Feature Flag Event when rolling out
* Update apps/vscode-rollout/scripts/smoke-loader.mjs
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
The 4.0.0 SDK migration routed Ollama through the generic OpenAI-compatible
vendor (/v1/chat/completions), which cannot express Ollama's options.num_ctx.
Every model loaded at Ollama's 4096-token server default, truncating Cline's
prompt and breaking most features (CLINE-2603, CLINE-2566, CLINE-2572).
- Add a native Ollama vendor backed by ai-sdk-ollama (wraps the official
ollama client); num_ctx derives from the resolved gateway model's
contextWindow at the adapter boundary, defaulting to 32768
- Persist the Model Context Window setting in providers.json via the
pre-existing provider-neutral contextWindow field (legacy
ollamaApiOptionsCtxNum state key kept as read fallback / write mirror),
and surface it as the selected model's contextWindow so the chat
indicator, compaction budgets, and num_ctx all agree
- Project ProviderConfig.maxInputTokens (where ProviderSettings.contextWindow
lands) onto the selected gateway model in both gateway builders so
CLI/Core hosts honor the configured value too
- Stop falling back to the bundled Ollama-Cloud catalog when /api/tags is
empty; local-model-source providers keep the user's committed model
instead of silently selecting a cloud model (nemotron)
- Wire Request Timeout (ms) with the legacy semantics (response must start
within requestTimeoutMs || 30000; streaming never cut off mid-generation)
- Settings UI: gate the context-window field until provider config loads,
skip unchanged writes, drop the custom prompt checkbox
Fixes CLINE-2603, CLINE-2566, CLINE-2572
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
* fix(vscode-rollout): align bundle versions in the stable AB workflow
Found by Max in local testing: the union manifest's version (what the
Marketplace and auto-update see) is the stitch input, but each bundle's
About tab and telemetry extension_version read that bundle's OWN
package.json — so the stable combined VSIX reported three different
versions (dispatch input / main's 4.0.0 / legacy's 4.0.8) depending on
where you looked. The nightly channel doesn't have this problem
(nightlify.mjs stamps one version into everything); this gives the stable
channel the identity-preserving equivalent: scripts/set-version.mjs stamps
the dispatch version into each checkout after install, before its build.
Also fixes a latent ab-package bug while restructuring the steps: the
next-bundle build never ran build:sdk, so the @cline/* workspace deps had
no dist and esbuild would fail on a fresh CI checkout (the workflow has
never run end-to-end — the publish environment gate blocked pre-merge
dispatches). Split install/build:sdk/align/build into separate steps,
mirroring the nightly workflow.
* fix(vscode-rollout): assert bundle sub-manifest versions in identity guardrails
Greptile round on #12321: the stable guardrail didn't assert version at
all. Went one further than the suggestion — both workflows' guardrails now
also assert each bundle sub-manifest's version (and name, for nightly)
matches the expected version, which is the check that actually regression-
guards the set-version.mjs/nightlify.mjs stamping (About tab + telemetry
extension_version read the sub-manifests, not the union). Expected version
routed through env rather than interpolated into the script body. Adds the
conventional paired test for set-version.mjs.
* fix(vscode-rollout): don't fail the nightly run when the tag push is rejected
First real combined publish (run 29454994164) published to both registries
successfully but the run went red at the last step: the default
GITHUB_TOKEN cannot create a ref whose commit modifies workflow files, and
HEAD was the #12253 squash merge which rewrote this very workflow. There
is no workflows permission grantable to the token, so this recurs any
night HEAD touched .github/workflows. The tag is bookkeeping — mark the
step continue-on-error so a successful publish isn't reported as a
failure. (Today's missing tag was pushed manually.)
* feat(vscode-rollout): A/B loader and packaging for staged SDK extension rollout
Ship one marketplace VSIX containing a tiny loader plus two complete
extension bundles: next/ (SDK-based apps/vscode from main) and legacy/
(the legacy-extension branch). The loader picks one bundle per window
from a PostHog-flag-driven, sticky, one-way cohort assignment, activates
it with a Proxy-scoped ExtensionContext so each bundle resolves its
resources from its own subdirectory, and falls back to legacy (with
partial-registration cleanup and version pinning) if the next bundle
crashes during activation.
Includes the union-manifest generator with per-cohort when-clause
gating, the VSIX stitcher, a node-level loader smoke test, and the
ext-vscode-ab-package workflow that builds both refs and packages
(optionally publishes) the combined VSIX.
* fix(vscode-rollout): address rollout review feedback
* feat(vscode-rollout): versioned kill-switch, user-setting override, launch-cadence telemetry
Review follow-ups from #12253:
- Kill-switch is now scoped by version instead of boolean: the PostHog flag's
payload carries {"maxKilledVersion": "x.y.z"} and the loader demotes only
combined VSIXes <= that version, so killing a broken release never blocks
the release that fixes it. Arming with no payload still demotes everything,
and the old boolean memento format is normalized on read.
- cline.rollout.bundleOverride user setting (auto | next | legacy) as a
manual escape hatch editable straight from settings.json: beats flags and
the kill-switch in both directions, applies on window reload, reported as
'override' on the activation event. Injected into the union manifest by
gen-manifest so neither bundle has to know about it.
- parseRolloutFlags hardens flag typing: only a literal boolean true promotes
(multivariate variants, numbers, junk fail safe), kill payloads are parsed
defensively from /decide's JSON-string encoding.
- Activation events now carry ms_since_last_activation so the real window-
reload cadence bounds how fast the rollout percentage gets dialed up.
- Walkthrough manifest invariant relaxed from byte-equality to structural
equality (ids/media/completionEvents): the branches already diverge on one
MCP step description, and since walkthrough markdown at the VSIX root comes
from next regardless, hard-failing on copy tweaks bricked the release
pipeline while protecting nothing. Copy divergence now warns and ships
next's text.
* feat(vscode-rollout): identity-aware namespace, authoritative activation telemetry, nightly indicator
- Derive the setting section and sdkBundle context key from the packaged
manifest name (cline.* for stable claude-dev, cline-nightly.* for the
nightly identity, whose packaging rewrites the whole ID namespace);
gen-manifest derives the same prefix for gates and the injected
bundleOverride setting.
- Call the activated bundle's reportRolloutActivation export (merged on
both branches) with attempted/actual/fallback — the authoritative
extension.rollout.bundle_activated event, attributed via the bundle's
variant-built telemetry. On crash fallback the LEGACY bundle reports it.
- Rename the loader's direct PostHog event to
extension.rollout.loader_decision: it collided byte-for-byte with the
bundles' event name under a different schema. It keeps the loader-side
metadata (override, launch cadence, loader_version, extension_name) and
gains double_failure for the both-bundles-dead case.
- Fix duplicate activation events on crash fallback: the recursive legacy
activation no longer emits a second, contradictory fallback:false event.
- Nightly-only status bar indicator (Cline: Next / Cline: Legacy) so
dogfooders can see which bundle a window is running.
- Union diverged engines to the newer requirement instead of hard-failing:
main's VS Code engine (^1.101.0) has legitimately moved ahead of
legacy-extension's (^1.84.0), which bricked every combined build.
- Smoke scenarios for all of the above.
* feat(vscode-rollout): publish the nightly as the combined A/B VSIX
Convert ext-vscode-publish-nightly.yml (cron + dispatch) from the
standalone SDK build to the combined loader + next + legacy package,
published as saoudrizwan.cline-nightly at <major>.<minor>.<unix-seconds>:
- scripts/nightlify.mjs reproduces publish-nightly.mjs's identity mutation
(claude-dev -> cline-nightly, "cline. -> "cline-nightly., displayName,
activity bar title) with the version as an explicit argument so ONE
version reaches both bundle manifests and the union manifest. Runs after
dependency install and before each bundle build.
- Both bundle builds get CLINE_ROLLOUT_VARIANT (next/legacy) in the nightly
AND stable workflows — without it the merged rollout telemetry
(extension_variant common prop + the authoritative bundle_activated
capture) silently no-ops.
- dry-run dispatch input builds and uploads the installable .vsix without
publishing or tagging; publish/tag steps are additionally gated to main,
so the PR branch can be dispatched for pre-merge verification.
- Identity guardrails before packaging: nightly workflow asserts
cline-nightly, the stable ab-package workflow asserts claude-dev.
- The nightly tag now records the legacy bundle sha in its message.
- README: nightly channel section (identity mapping, the two telemetry
events and their owners, dry-run verification), and a note that the
PostHog flags govern nightly only until the stable combined VSIX ships.
The single-bundle publish-nightly.mjs path remains for manual
feature-branch pre-release publishes; CI no longer invokes it.
* chore(vscode-rollout): harden nightly workflow gating
- Restore a job-level branch allowlist on the publish job (main + the
rehearsal branch). Advisory defense-in-depth: the enforced gate is the
PublishNightly environment's deployment-branch policy in repo settings,
which must list the same branches; a dispatched branch runs its own copy
of this file.
- Route the legacy-ref dispatch input through env instead of interpolating
it into the run script body (script-injection hygiene; dispatch already
requires write access).
* add otel vars to rollout build (#12316)
- Extension will not emit otel metrics to otel without these vars, so
adding those into the slow-rollout build workflow
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* fix(vscode-rollout): pass OTel env to the nightly legacy bundle build
Legacy's esbuild inlines OTEL_* at build time and its standalone publish
workflow passes them, so the combined nightly's legacy bundle was being
built with the OTel logs/metrics pipeline dead. Companion to #12316,
which fixes the same gap in ext-vscode-ab-package.yml (both bundles
there).
* feat(vscode-rollout): make the rollout two-way, remove the kill-switch
The one-way cohort + versioned kill-switch existed to avoid demoting users
whose SDK-bundle tasks aren't listed by legacy and whose rotated creds may
need a re-login. Decision: those are acceptable, temporary UX costs on an
emergency-only path — not worth a second flag and permanent mechanism
complexity (payload parsing, version scoping, killed-up-to cache format).
Now there is ONE knob: each background refresh caches exactly what
ext-sdk-bundle-rollout says for the next window. Dialing the percentage
down demotes; 0% pulls everyone back to legacy on their next reload.
Fail-safe direction preserved: only a literal boolean true promotes —
variant strings / numbers / a deleted flag all resolve to legacy; malformed
/decide responses leave the cache untouched. Local crash pinning (next
threw -> pin this version to legacy on this machine) is unchanged and
independent of the flag.
Removes KILLSWITCH_FLAG/KILLSWITCH_STATE_KEY/isVersionKilled/
normalizeKilledUpTo/compareVersions/nextCachedBundle; parseRolloutFlags
becomes parseRolloutAssignment returning the bundle to cache. Smoke
scenarios replaced with two-way promote/demote coverage.
---------
Co-authored-by: Max <maxpaulus43@gmail.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
* fix: update broken ACP editor integrations redirect to point to CLI reference
* feat: add ACP Editor Integrations page under CLI section
- Create cli/acp-editor-integrations.mdx with ACP overview, supported editors, quick start, and usage guide
- Add page to CLI navigation group in docs.json
- Restore redirect from /cline-cli/acp-editor-integrations to /cli/acp-editor-integrations (page now exists)
* Revert "feat: add ACP Editor Integrations page under CLI section"
This reverts commit 2728b9c2ad.
* fix(telemetry): attach organization context to cached-credential identity
CLI cached credentials only stored the account id, so telemetry identity
resolved from them (headless runs via #11581, the hub daemon via #12177)
carried user_id but no organization_id - making CLI/hub usage invisible
to organization-scoped dashboards even where per-user attribution works.
- AuthSettingsSchema gains optional organizationId/organizationName/
memberId
- loadClineAccountSnapshot persists the active organization into the
cached cline provider settings after fetching /me (cleared when the
user is on their personal account), so the context survives across
processes without a network call
- the CLI runtime identify and the hub daemon identity refresh read the
persisted fields and pass them to identifyAccount; the daemon re-keys
its refresh on account+organization so an org switch re-identifies a
long-lived daemon
* fix(telemetry): strip stray NUL byte, drop needless reshaping of daemon identity resolve
* fix(core): normalize read file request path aliases
Accept `file_path` and `filePath` in read file requests and normalize them to the canonical `path` field. Apply alias handling to direct, array, and nested inputs to prevent model-generated variants from failing validation.
Clarify path descriptions by removing redundant wording.
* update test
* fix(cli): prevent use-after-free when setting terminal title during TUI teardown
* fix(cli): re-check renderer destruction before title reset in teardown microtask
* test(cli): cover terminal title teardown lifecycle
* fix(core): stop reporting benign git states as workspace init errors [ENG-2244]
A freshly initialized repo with no commits makes 'git rev-parse HEAD'
fail, which generateWorkspaceInfoWithDiagnostics recorded as a workspace
init error and surfaced as workspace.init_error telemetry on every
session bootstrap. Filter out git failures that reflect normal
repository states; genuine failures (missing directory, real git
breakage) are still reported.
* fix(core): drop 'bad revision' from benign git error filter
Review feedback: 'fatal: bad revision HEAD' can also indicate a corrupt
.git/HEAD (checkIsRepo still succeeds), which is a genuinely broken
workspace that should keep reporting. The remaining patterns cover the
empty-repo message variants.
* fix(vscode): restore multi-root mention resolution and validate stored task cwd [ENG-2245][ENG-2244]
The SDK adapter's ensureWorkspaceManager() was a stub returning
undefined, which silently disabled multi-root file mention resolution:
parseMentions only searched the primary cwd, so @-mentions of files in
secondary workspace roots failed with not_found. Build a real
WorkspaceRootManager from the host's workspace folders (cached until
the folder set changes) via a new WorkspaceRootManager.fromPaths().
Also validate that a resumed task's stored cwdOnTaskInitialization
still exists before using it — stale paths (deleted/moved dirs) fed
git-based workspace init and produced init-error telemetry.
* fix(vscode): use JSON.stringify for workspace manager cache key
Review feedback: a delimiter-joined key is ambiguous for paths
containing the delimiter (and the previous separator was an embedded
NUL byte). JSON.stringify is unambiguous and order-preserving.
* test(vscode): cover stored task cwd validation
* fix(vscode): use the requested provider's stored credentials when listing OpenAI-compatible models
The OpenAI-compatible settings pane already fetches GET <baseUrl>/models to
suggest model IDs, but the host handler always read the built-in "openai"
provider's stored settings. Custom OpenAI-compatible providers only expose a
masked API key to the webview, so their model-list requests went out
unauthenticated and the suggestion dropdown stayed empty.
Add provider_id to OpenAiModelsRequest and read that provider's stored API
key and custom headers in refreshOpenAiModels. Old clients omit the field,
which defaults to "openai" and preserves the previous behavior.
* feat(cli): suggest model ids from OpenAI-compatible endpoints in the model picker
The CLI showed a bare free-text input for openai-compatible providers and
never asked the endpoint what it serves. Fetch GET <baseUrl>/models with the
provider's stored API key/headers when opening the picker; when the endpoint
answers, show the standard fuzzy list (which keeps the "Create custom model
ID" row for manual entry). Any failure or empty answer falls back to the
existing free-text input.
* fix: resolve OpenAI-compatible model discovery config
* feat(shared): move plan/act mode prompt instructions into the shared prompt builder
The CLI's #12057 fixes (mode-tag explanation, plan-mode contract,
mode-switch notice tracker) were CLI-only wiring, so the VSCode extension
never told the model what the <user_input mode> attribute means and plan
mode kept mutating files (CLINE-2576, CLINE-2607, CLINE-2579). Promote
the pieces every host needs into @cline/shared:
- buildClineSystemPrompt now appends MODE_TAG_INSTRUCTIONS for every mode
and PLAN_MODE_INSTRUCTIONS for plan sessions, composed into the rules
slot in the exact order the CLI historically built by hand, so CLI
output is byte-identical after the refactor.
- The plan-mode contract gains an explicit run_commands paragraph:
the tool intentionally stays available in plan mode (essential for
read-only investigation) but is inspection-only there -- no file
mutations, no state-changing commands. The mitigation for plan-mode
mutations is prompting plus mode-switch notices, not tool removal.
- createModeSwitchNoticeTracker moves from apps/cli/runtime/interactive
to @cline/shared next to formatModeSwitchNotice; the CLI re-exports it
so its import surface and tests stay unchanged.
- deriveTitleFromPrompt gets a regression test pinning that titles never
pick up mode-notice text.
* fix(vscode): teach the model about plan/act modes and surface mode switches
Port the CLI's #12057/#12058 plan-mode fixes to the extension:
- The session factory drops its local PLAN_MODE_INSTRUCTIONS copy; the
shared prompt builder now emits both the mode-tag explanation and the
plan-mode contract (including the read-only run_commands rule), so the
extension's system prompt finally explains the <user_input mode>
wrapper its own messages have carried all along.
- Manual Plan/Act toggles record a mode-switch notice in
SdkModeCoordinator (shared round-trip-cancelling tracker, scoped to
the rebuilt session so it never leaks across tasks), recorded only
after the session replacement actually commits. The model-initiated
switch_to_act_mode path passes source: "tool" and records nothing,
matching the CLI: its tool result and continuation prompt already
announce the switch.
- SdkSessionLifecycle.fireAndForgetSend -- the single funnel for
outbound turn sends -- consumes the notice and prepends
formatModeSwitchNotice() to the next message, exactly like the CLI's
run-interactive stamping.
- Display boundaries never render the raw tag: the queued-prompt echo
in the message translator now goes through formatDisplayUserInput,
and isSyntheticUserPrompt strips notices before matching so a stamped
continuation prompt cannot shift edit/regenerate ordinals.
* feat(sdk): expose edit-executor internals for host diff previews
Extract computePatchChanges() from createApplyPatchExecutor so hosts can
compute a patch's per-file proposed content without writing to disk
(behavior-identical refactor; the executor now calls the helper), and
widen the @cline/core root exports with createEditorExecutor,
createApplyPatchExecutor, computePatchChanges, PatchActionType and the
related types. Needed by the VS Code adapter to restore the editor diff
view for SDK edit tools.
* fix(vscode): restore editor diff view for SDK edit tools
Adds SdkDiffEditCoordinator, which owns per-toolCallId diff sessions over
the legacy DiffViewProvider abstraction (HostProvider factory, so the
external/JetBrains gRPC DiffService path keeps working):
- the diff editor opens populated before the approval ask renders (the
SDK surfaces tool input only after the model stream completes, so the
approval callback is the only pre-execution point with full input)
- an overridden editor executor saves through the diff document:
user edits in the editable right pane and post-save auto-formatting
flow back to the model via formatResponse.fileEditWithUserChanges,
plus 'new problems' diagnostics
- Reject/abort reverts (new files: file + created dirs removed)
- auto-approved edits open the diff during execution with the legacy
3.5s diagnostics settle; Background Edit keeps the headless disk path
- apply_patch gets a preview-only diff of its first changed file; on
approve the preview is reverted and the untouched SDK executor applies
the whole patch
- any diff-pipeline failure reverts and falls back to the SDK disk
executor, preserving canonical error strings
Fixes#11934 (CLINE-2580).
* refactor(vscode): make edit diff preview a read-only virtual-document diff
Reworks the diff view restoration after EDH testing showed the editable
real-document design breaking on same-file multi-edits (tab reuse opened
the actual file instead of a diff; sibling saves closed other sessions'
tabs; right-pane edits misbehaved).
New design per review:
- EditPreview abstraction (mirrors CommentReviewController pattern):
VscodeEditPreview renders vscode.diff with BOTH sides as virtual
cline-diff documents (unique fragment per preview, so same-file edits
get distinct tabs and close is an exact tab match, never the real
file); ExternalEditPreview uses the existing openMultiFileDiff/
closeAllDiffs host-bridge RPCs. New createEditPreview factory on
HostProvider.
- The preview never touches disk: executors close the preview and
delegate to the SDK's default disk executors, whose results and error
strings reach the model unchanged. Reject/abort just closes a tab.
- Dropped by design decision: editing in the diff view, user-edit
feedback to the model, and diagnostics passback (the SDK already
prompts the model to check).
- Auto-approved edits show a brief preview that lingers ~1.5s after the
write; an abort cuts the linger short without failing the applied edit.
- A newer same-file preview supersedes an older pending one (approvals
resolve sequentially), eliminating cross-session interference.
- Legacy DiffViewProvider stack returns to untouched dead code.
* fix(vscode): state that denied edits did not modify the file
Repro: ask Cline to edit a file, then answer the approval with feedback
instead of Approve/Reject. The denial reached the model as just
{"error":"make them bigger"} — nothing said the edit was NOT applied —
so the model treated the feedback as iteration on an applied change and
built its next old_text against content that never landed on disk. From
then on old_text no longer matched the real file and the diff preview
silently stopped appearing (and the eventual executor run would fail the
same way).
Denial reasons now come from buildToolApprovalDenialReason(): edit tools
get 'The user denied this edit. The file was NOT modified and still
contains its original content.' (legacy parity), and all tools get user
feedback wrapped in <feedback> tags instead of the bare prompt as the
whole reason. isKnownToolApprovalDenial also matches the new edit-denial
marker so translator suppression keeps working.
* feat(vscode): simulated streaming animation for edit previews
Brings back the legacy 'yellow sweep' feel on the virtual diff preview.
The SDK only surfaces complete tool input, so this is a deliberate
simulation of the legacy streaming look (which legacy also showed when
it already had the full content in memory).
The sweep covers the whole file like legacy did, with diff-aware pacing:
- Park at the top: whole document under the faded-yellow overlay, cursor
highlight on line 0, viewport pinned to the top, ~400ms hold so the
animation unambiguously starts from the top.
- Zip through unchanged spans in small fast steps (~8 lines per 16ms
frame, capped per span) so they read as continuous motion.
- Slow down through each change: one line per 45ms frame with a ~350ms
minimum dwell per hunk so even a one-line change visibly pauses.
- Changed runs come from a real line diff (diffLines), so multi-hunk
edits slow at EACH hunk and the gaps between hunks zip; pure deletions
pause at the deletion point.
- Zip frames chase the cursor (InCenter) for continuous scroll; typing
frames scroll only when leaving the viewport (no per-frame judder).
- After the sweep reaches the bottom: short beat, then settle centered
on the first changed line for review.
Mechanics: edit previews move from base64-query cline-diff URIs to a new
mutable cline-edit-preview content provider (content set programmatically,
re-rendered via onDidChange) so the virtual right side can update in
place. DecorationController is reused as-is. The approval ask renders
while the animation plays (legacy simultaneity); close() cancels
mid-animation; files >3000 lines render the final diff immediately.
External hosts keep the static openMultiFileDiff preview.
* chore(vscode): remove test artifact comment from memory-monitor
* fix(vscode): address review nits — skip diff computation for large files, close partially-opened previews
- buildEditPreviewAnimation (which runs a full line diff) now runs after
the MAX_ANIMATED_LINES guard; oversized files use a cheap prefix scan
just to aim the viewport.
- If preview.open() throws after partially opening, the tab is closed
directly — the session was never registered, so discardPreview could
not have reached it.
* fix(vscode): keep tsconfig valid JSON for test setup
* fix(vscode): bound diff preview animation
* Store startedAt in auth metadata when starting a Cline session
* Inject the sessionStartedAt when creating the auth credentials
* Remove injecting sessionStartedAt when it's not stored already
* Address review
* fix merge inconsistencies
The custom MarkdownCode node type used a narrow { metastring?: string }
shape that is not assignable from the hast Element passed by
react-markdown/streamdown, so a clean rebuild (fresh dependency resolve,
as done by the release version.ts) fails the `satisfies Components`
check. Widen node.properties to Record<string, unknown> and validate the
metastring value at read time.
* feat(cli): manual API key escape hatch for Cline OAuth providers
Add a way to configure the cline / cline-pass providers with a dashboard
API key from the /settings provider flow, for users where OAuth login
isn't working:
- "Enter API key manually" option in the already-configured dialog
- K keybinding in the OAuth login dialog to switch to key entry
- Saving clears stored OAuth tokens (on both the shared cline storage
entry and any direct cline-pass entry) since the auth handler prefers
auth.accessToken over apiKey — a stale token would otherwise keep
winning over the manual key
- isProviderConfigured now counts a persisted API key for OAuth
providers so escape-hatch users aren't forced back into OAuth on
every provider switch
* fix(cli): move API key fallback to OAuth dialog
* fix(sdk): stop misclassifying transient refresh failures as invalid_grant
getValidClineCredentials returned null for BOTH a rejected refresh token and
any transient error (network down, timeout, 5xx) that happened to land after
the access token expired. Callers treat null as 'session dead' — the
extension wipes providers.json over it, logging out every Cline process on
the machine, which is what CLI users then hit as 'Unauthorized: please
re-authenticate'. A laptop waking from sleep past the ~1h token expiry with
a background job (balance/banners/remote-config) refreshing before the
network is up was enough to trigger it — no refresh-token rotation involved.
Now: null means the refresh token was REJECTED (re-auth required); transient
failures throw so callers keep stored credentials and retry later. The
extension's refreshAccessToken catch and the CLI's error surface already
handle the throw correctly with no changes.
* fix(sdk): write providers.json atomically
providers.json was written with a bare writeFileSync while being read
concurrently by every other Cline process (CLI, extension, hub). A reader
catching a partial write parses garbage, which read() silently treats as
EMPTY settings — indistinguishable from being logged out — and any
subsequent save from that process persists the empty state, erasing every
configured provider.
Stage to a pid-unique temp file and rename into place; rename is atomic on
POSIX and replaces on Windows, so readers only ever see a complete file.
* feat(telemetry): track auth refresh outcomes to measure the hard-logout fix
Adds the observability needed to verify in production that the
transient-vs-invalid_grant fix is working, and to diagnose any logouts that
remain:
- user.auth_refresh_soft_failure — fires when a refresh fails for a reason
that does NOT invalidate the session (network error, timeout, 5xx) and
stored credentials were kept. Instances with tokenExpired=true were hard
logouts before the fix, so this is the 'prevented logout' counter. Emitted
from the SDK (CLI path) and from the extension's refresh/restore catches
under the same event name so dashboards aggregate both clients.
- user.auth_logged_out now carries the HTTP status and errorCode that caused
it, and the extension emits it (with a distinct reason) at every site that
clears providers.json: refresh_rejected, restore_refresh_rejected, and
handleDeauth's LogoutReason (user_initiated / cross_window_sync / …), which
was previously accepted and ignored. Extension-triggered logouts were
completely invisible before — including the legacy-extension cross-window
cascade, which this now measures directly.
Success looks like: auth_logged_out volume drops after release while
auth_refresh_soft_failure appears in its place, and any remaining logouts
carry a reason/status we can act on.
* fix(telemetry): route auth refresh events through SDK
* fix(sdk): stop misclassifying transient refresh failures as invalid_grant
getValidClineCredentials returned null for BOTH a rejected refresh token and
any transient error (network down, timeout, 5xx) that happened to land after
the access token expired. Callers treat null as 'session dead' — the
extension wipes providers.json over it, logging out every Cline process on
the machine, which is what CLI users then hit as 'Unauthorized: please
re-authenticate'. A laptop waking from sleep past the ~1h token expiry with
a background job (balance/banners/remote-config) refreshing before the
network is up was enough to trigger it — no refresh-token rotation involved.
Now: null means the refresh token was REJECTED (re-auth required); transient
failures throw so callers keep stored credentials and retry later. The
extension's refreshAccessToken catch and the CLI's error surface already
handle the throw correctly with no changes.
* fix(sdk): write providers.json atomically
providers.json was written with a bare writeFileSync while being read
concurrently by every other Cline process (CLI, extension, hub). A reader
catching a partial write parses garbage, which read() silently treats as
EMPTY settings — indistinguishable from being logged out — and any
subsequent save from that process persists the empty state, erasing every
configured provider.
Stage to a pid-unique temp file and rename into place; rename is atomic on
POSIX and replaces on Windows, so readers only ever see a complete file.
Next 16 blocks dev-resource requests (/_next/webpack-hmr, dev fonts) from
origins that don't match the dev server's own hostname. Browsing the web
dev mode via 127.0.0.1 left the page hanging with 'Blocked cross-origin
request to Next.js dev resource' warnings. allowedDevOrigins is dev-only,
so production/Tauri builds are unaffected.
* feat(desktop-app): env-configurable sidecar bind host, trusted origins, and webview WS endpoint
Allows running the desktop app's web dev mode (dev:web + dev:sidecar) inside
a Docker container with published ports:
- CLINE_SIDECAR_HOST: sidecar bind hostname (default remains 127.0.0.1)
- CLINE_SIDECAR_TRUSTED_ORIGINS: comma-separated extra browser origins for
the sidecar's origin allowlist (validation itself stays on)
- NEXT_PUBLIC_SIDECAR_WS_ENDPOINT: overrides the webview's hardcoded
ws://127.0.0.1:3126/transport fallback so a browser on the Docker host can
dial the published port
All defaults are unchanged, so local/Tauri behavior is unaffected when the
env vars are absent. When bound to 0.0.0.0 the printed ready endpoint
advertises 127.0.0.1 since a wildcard bind is not dialable.
* chore(desktop-app): untrack next-env.d.ts
It was added to .gitignore previously but never removed from the index, so
it kept showing as modified: Next.js rewrites the routes.d.ts import path
depending on whether 'next dev' or 'next build' ran last. The file is
regenerated by Next on every dev/build run, and the app's typecheck
(tsconfig.dev.json) excludes webview/, so nothing needs it tracked.
* style(desktop-app): format SIDECAR_HOST declaration
* Add the ClinePass limit error to the CLI
* Update apps/cli/src/runtime/run-agent.test.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* format code and improve instructions
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* fix(auth): add early SDK debug logging for Cline credential lifecycle (ENG-2213)
Adds targeted debug-level logging at key points in the Cline/Cline Pass
credential lifecycle to diagnose intermittent logout issues. Credentials
are never logged in cleartext; an 8-hex-digit SHA-256 hash is used instead.
The SDK has two logger layers:
1. ClineCore.logger — session-scoped, threaded from ClineCore.create({logger})
into session config and the agent event bridge.
2. setSdkLogger() — early/module-level, for components that operate before
or outside of ClineCore sessions: ProviderSettingsManager (constructed
at startup), RuntimeOAuthTokenManager, and cline.ts auth functions
(token refresh). These can't be reached by the session-scoped logger.
Both VS Code (common.ts) and CLI (main.ts) call setSdkLogger() once at
startup. When no logger is registered (or the host filters out debug),
every call is a no-op — logging is never collected in normal use.
Instrumentation points (SDK core, shared by both surfaces):
- ProviderSettingsManager.read(): logs provider IDs, last-used, and whether
Cline auth is present (with hashed access/refresh token fingerprints)
- ProviderSettingsManager.saveProviderSettings(): logs the provider being
saved, tokenSource, whether Cline auth was present before/after, and
flags authDropped when a previously-present Cline auth block disappears
- RuntimeOAuthTokenManager.resolveProviderApiKeyInternal(): logs each
decision point (no_settings, no_credentials, refresh_start, refresh_null,
refreshed+saved, not_refreshed) with hashed token fingerprints
- cline.ts refreshClineToken(): logs the refresh request URL, response
status/errorCode on failure, and new token hashes on success
- cline.ts getValidClineCredentials(): logs the outcome at each branch
(no_current_credentials, still_valid, needs_refresh, invalid_grant,
transient_failure_kept_current, transient_failure_expired)
VS Code extension (auth-service.ts):
- readClineCredentials/writeClineCredentials/clearClineCredentials: logs
credential presence and hashes at each disk I/O point
- refreshAccessToken: logs refresh start, null result (cleared), changed
(written), or unchanged outcomes
- fetchUserInfoFromApi: logs the GET /api/v1/users/me request and response
status
What to collect when investigating:
VS Code extension:
- Open the "Cline" output channel (View -> Output -> select "Cline")
- Look for lines containing: [SdkAuthService], providers.read,
providers.save, oauth.resolve, cline.refresh, cline.getCredentials
- Debug logging is emitted at the DEBUG level; it appears in the output
channel when IS_DEV=true or in development builds
CLI:
- Set CLINE_LOG_LEVEL=debug environment variable before running cline
- Collect the log file at ~/.cline/data/logs/cline.cli.log (or the path
set by CLINE_LOG_PATH)
- Look for the same event names as above
Files changed:
- sdk/packages/core/src/auth/auth-debug.ts (NEW): hashSecret,
setSdkLogger, getSdkLogger, sdkDebug
- sdk/packages/core/src/auth/cline.ts: refresh/getCredentials logging
- sdk/packages/core/src/services/storage/provider-settings-manager.ts:
read/save logging
- sdk/packages/core/src/runtime/orchestration/runtime-oauth-token-manager.ts:
resolve logging
- sdk/packages/core/src/index.ts: export early logger utilities
- apps/vscode/src/sdk/auth-service.ts: credential lifecycle logging
- apps/vscode/src/common.ts: register SDK early logger
- apps/cli/src/main.ts: register SDK early logger
* fix(vscode): inline SDK debug metadata into log message string (ENG-2213)
* fix(auth): gate debug logging on CLINE_LOG_LEVEL at runtime (ENG-2213)
* fix(auth): use interpolated debug strings, remove log-level gating (ENG-2213)
* refactor: move early logger to sdk/packages/core/src/logging/early-logger.ts
* fix: address review feedback — early logger registration, log after write, remove getSdkLogger from public API
* fix(vscode): add ISO timestamps to all log lines
* fix core import
* fix import
* fix tests
---------
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: BarreiroT <tomasmbarreiroi@gmail.com>
The webview already knew how to render readLineStart/readLineEnd on
readFile tool rows, but the SDK message translator never populated
them, so successive ranged reads of the same file all rendered as
identical bare paths. Extract start_line/end_line from read_files
input (per-file and single-path forms) and render open-ended reads
(start_line only) as "start+".