Conversation
Claude Code mode signs in through the Claude CLI, which accepts a subscription login, but inline completions call the Anthropic API directly and need a credential of their own. With none available the SDK raised TypeError while building each request, so every pause in typing left a fresh traceback in the server log. Model selection now asks whether the SDK resolved a credential before choosing the Claude auto-complete model, and leaves the feature off when it did not, which also skips the context building that precedes the request. The check runs the SDK's own header validation rather than inspecting an API key, because an attribute check would disable auto-complete for deployments where it works today: a profile, the workload-identity variables, and a credential passed through ANTHROPIC_CUSTOM_HEADERS all leave api_key and auth_token unset while requests authenticate. Every step that cannot be carried out confidently allows the request, including a validator of another shape, so an SDK change cannot silently switch the feature off. A credential that carries nothing counts as absent. A blank ANTHROPIC_AUTH_TOKEN, a whitespace value, or a blank custom header all leave the SDK willing to send an empty auth header, and the API answers 401 apiece, which is the same flood in a different exception. The warning is emitted once per server run, shared by the selection and request paths, and re-arms when a credential reappears so a key removed later is not silent. Selection re-runs on every capabilities request, so a per-model flag would coalesce nothing. Model construction is wrapped as well: the SDK reads profile files while resolving credentials, so a bad ANTHROPIC_CONFIG_DIR raised there and failed the whole capabilities response over one feature's misconfiguration.
…rmless The Anthropic credentials row considered only ANTHROPIC_API_KEY, so a server authenticating with ANTHROPIC_AUTH_TOKEN was reported as having no credential. _credential_source now takes several environment variables and the row accepts either. The row also spoke only about chat turns, while a missing credential definitely disables auto-complete. It now names auto-complete alongside chat in the remedy, but deliberately does not assert that either is off: this check sees one settings field and two environment variables, while the SDK also authenticates from a profile, the workload-identity variables, and custom headers, so claiming a feature is disabled would be false on exactly those deployments and would send an operator looking for a credential they already have.
Records the fix in the unreleased 5.4.0 section and adds a troubleshooting entry for the state a keyless Claude-mode user lands in: what counts as a credential, that a process environment variable has to be exported before JupyterLab starts, that a blank value does not count, that an administrator-pinned auto-complete model leaves supplying a credential as the only remedy, and that a misconfigured profile logs a different message than a missing credential does.
pjdoland
force-pushed
the
fix/425-inline-completion-missing-key
branch
from
September 14, 2026 13:15
8618ff5 to
11f3b09
Compare
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.
Summary
In Claude mode, a user whose Claude CLI holds a subscription login has no Anthropic API key for NBI to find, and auto-complete crashed on every pause in typing. Claude mode's chat goes through the CLI, but inline completions call the Anthropic API directly, and the Anthropic SDK raises only when it builds a request, so the model was constructed successfully and then failed on every call:
Nothing degraded gracefully: each debounced keystroke pause produced a fresh traceback in the server log. The reporter's log shows four in 70 seconds, and the default settings reach this state with no explicit user choice, because the Claude auto-complete dropdown defaults to "Default (recommended)", which is a concrete model rather than off.
Solution
Decline the model instead of failing per request.
update_models_from_confignow asks the model whether it can authenticate before selecting it, and leaves_inline_completion_modelunset when it cannot, sohandle_inline_completionsreturns before building any context. A second guard insideinline_completionscovers callers that construct the model directly, such as extensions.Ask the SDK, do not re-derive its precedence. The load-bearing decision is
_client_can_authenticate. An earlier draft read the client'sapi_keyandauth_token, which was wrong in three ways: on current SDKs Anthropic also authenticates from a credentials provider (ANTHROPIC_PROFILE, a config-dir profile, or the workload-identity variables) and from anX-Api-KeyorAuthorizationheader supplied throughANTHROPIC_CUSTOM_HEADERS, and each of those leaves both attributes unset while requests succeed. Judging by attributes would have switched auto-complete off for deployments where it works today. The check now calls the SDK's own header validation, the code that raises the error above, with the client's merged default headers.Every uncertain step allows the request, because a wrong "no" (auto-complete off for a working deployment) is worse than a wrong "yes" (the error this guard exists to prevent). A validator that is absent, that will not accept two header arguments, or headers that are not a mapping all read as authenticated. The argument count is checked with
inspect.signature(...).bind(...)rather than assumed: treating a signatureTypeErroras if it were the authentication one would disable a working setup on an SDK whose validator changed shape, which is a failure this guard demonstrably had before that check was added.A credential that carries nothing reads as absent, a deliberate divergence from the SDK.
ANTHROPIC_AUTH_TOKEN=(how .env and compose files usually spell "unset"), a whitespace-only value, and a blank credential passed throughANTHROPIC_CUSTOM_HEADERSall leave the SDK willing to send an empty auth header; the API then answers 401 for every completion request, which is the same flood in a different exception. The settings panel already normalized whitespace, so this closes the gap where the identical value in the environment behaved differently.One warning per server run.
update_models_from_configre-runs on every/capabilitiesGET, and the front end refetches that on startup, after a settings save, and on every Claude CLI status change, building a fresh model each time. A per-model flag coalesced nothing, so the warning lives in a module-scope helper, matching the existinggithub_copilotdefault-password warning.A credential problem no longer fails an unrelated surface. The SDK reads profile files while resolving credentials, so a bad
ANTHROPIC_CONFIG_DIRraises inside the model constructor. That runs from the capabilities handler, so it is now caught and logged rather than failing the whole response.The Status card agrees with the server.
readiness.pyreported a missing key as harmless ("This is fine if the Claude CLI is signed in") and considered onlyANTHROPIC_API_KEY. It now acceptsANTHROPIC_AUTH_TOKENand says plainly that auto-complete is disabled, while keeping the subscription-login hedge for chat.Testing
None,"", or whitespace and raises the reportedTypeErroroninline_completions, onupstream/main._build_requestacross eight credential shapes: absent, blank api_key, blank auth token, both blank, a real key, a real auth token, a custom-header key, and a workload-identity provider. The shipped check agrees with the SDK everywhere except the intentional blank-credential case above.pytest tests/ --ignore=tests/test_claude_client.py: 1787 passed.tsc --noEmit, eslint, prettier, stylelint clean. jest: 423 passed across 33 suites (no frontend sources changed).~/.config/anthropic/configs/default.jsonwould authenticate them and make five of them fail on a developer machine that has one. Clearing the environment cannot reach that, and pointingANTHROPIC_CONFIG_DIRat an empty directory makes construction raise instead, so the fixtures stub the SDK's credential discovery. Measured with a fake home containing such a profile: discovery resolves a credential without the stub and resolves none with it, and the guard tests pass under that home. The one test that needs real discovery, the workload-identity case, restores it for its own duration.HOMEso the real config was untouched (NBIConfighardcodes~/.jupyter/nbi/config.json, soJUPYTER_CONFIG_DIRcannot redirect it). With Claude mode on and no credential: one warning naming the remedy, zero tracebacks, and typing past the debounce added no further log lines. With a placeholder key in the environment and the same config: no warning, and a real request to/v1/modelsreturning 401, which confirms the credential path was taken rather than skipped. Six/capabilitiesrefreshes produced one warning, where the pre-remediation build produced three. The readiness row rendered as "No Anthropic credential is visible to the server. Claude auto-complete is disabled."Risks / follow-ups
base_urlfronting a proxy that adds credentials server-side, with no key configured in NBI, is treated as unauthenticated. That configuration already failed onmainwith the sameTypeErrorper keystroke, so nothing working is lost, but there is no override to force auto-complete on.src/index.tskeys that off the configured setting rather than what the server resolved, and it still sends one request per debounce, which now returns immediately. Both predate this change (the same is true of thenonesetting today) and are worth a follow-up that surfaces the resolved state in the capabilities response.tests/test_claude_models.py::TestGetContextWindowfails intermittently in full-suite runs and passes in isolation, onupstream/mainas well as here. Nothing in this change touches litellm or that code path. The failures track machine load (that test's litellm import measured 1.66s idle and 4.87s under load, against the 30s per-test cap inpyproject.toml), and it failed once in a run where these new tests were explicitly deselected, so it is independent of this work. I could not reproduce the tipping deliberately, so I am not asserting a mechanism or attempting a fix.test_ai_service_manager_integration.py's_make_manager_for_update_testhelper builds a realAIServiceManager, which constructsClaudeCodeChatParticipantand spawns an actualclaudeCLI child process. Fourteen such tests predate this change and the four added here follow the same house pattern, so nothing new is introduced, but pointingNBI_CLAUDE_CLI_PATHat a nonexistent path in the test environment would make that file hermetic. That file's run-to-run timing also varies widely; I looked into it and could not establish a cause (removing the CLI from PATH and forcing the agent connect timeout to 0.1s both failed to reduce it), so I am reporting it as unexplained rather than guessing. The new tests themselves cost about half a second to a second per file, at or below the noise on this machine.Closes #425