anthropics / anthropics/claude-code

/login reports "Login successful" but token is never saved: ENOTDIR rmdir on stale .storage-write.lock file (2.1.277)

Open
#95,425 0 comments 0 reactions 0 assignees View on GitHub
area:auth bug has repro platform:macos
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code

### What's Wrong?

After updating to 2.1.277 (auto-update, 2026-09-18), every request failed with auth errors that cycled through `API Error: 401 OAuth access token has been revoked`, then `Anthropic profile login expired`, then `Not logged in · Please run /login`.

`/login` (in-session) and `claude auth login` (CLI) both complete the browser OAuth flow and print `Login successful`, but nothing is persisted. Immediately afterward:

- `claude auth status` → `"loggedIn": false, "authMethod": "none"`
- `security find-generic-password -s "Claude Code-credentials"` → item not found
- `~/.claude/.credentials.json` → does not exist

The Keychain itself is writable (verified with `security add-generic-password` / `find` / `delete` from the same shell). `claude doctor` reports "No installation issues found" while also reporting "Not signed in to claude.ai".

Root cause, from the debug log: the credential save fails because `~/.claude/.storage-write.lock` existed as a regular file rather than a directory, so the lock cleanup `rmdir` threw `ENOTDIR`. The OAuth exchange had already succeeded, so the UI reported success while the token was silently dropped. Deleting the stale file fixed it on the next `claude auth login`.

Full logout, Keychain item deletion, removing `~/.claude/.credentials.json` and `~/.config/anthropic`, and restarting did not help until the lock file was removed. Side effects while in this state: all MCP servers showed "needs authentication", remote managed settings failed to fetch, and Remote Control reported "Sign-in is missing the user:profile scope" even though the login URL requested that scope.

### What Should Happen?

Either of:

1. The credential store should tolerate a stale `.storage-write.lock` that is a regular file (unlink it, or use a lock strategy that can't be left behind as a file), so the token save succeeds.
2. At minimum, if the token save fails, `/login` and `claude auth login` should surface the error instead of printing `Login successful`. The `Failed to save OAuth tokens` error only appears in the `--debug` log, and `claude doctor` says "No installation issues found", so there is no user-visible signal pointing at the actual cause.

### Error Messages/Logs

```shell
From ~/.claude/debug/.txt during a /login in a `claude --debug` session:

2026-09-18T18:27:09.126Z [ERROR] Failed to save OAuth tokens: ENOTDIR: not a directory, rmdir '/Users//.claude/.storage-write.lock'
2026-09-18T18:27:09.747Z [DEBUG] [Bootstrap] Skipped: no usable OAuth, WIF, or API key
2026-09-18T18:27:28.457Z [ERROR] API error (attempt 1/11): Could not resolve authentication method. Expected one of apiKey, authToken, credentials, config, or profile to be set. Or for one of the "X-Api-Key" or "Authorization" headers to be explicitly omitted
2026-09-18T18:26:37.895Z [DEBUG] [Claude in Chrome] Disabled: OAuth token has no scope accepted by /api/oauth/validate (needs user:profile, user:office, or user:ccr_inference; ...)
2026-09-18T18:26:53.981Z [ERROR] [Anthropic telemetry] Failed to check metrics opt-out status: Auth error: no credential usable for the metrics opt-out check

CLI:

$ claude auth login
Opening browser to sign in…
Paste code here if prompted > Login successful.
$ claude auth status
{ "loggedIn": false, "authMethod": "none", "apiProvider": "firstParty", ... }

claude doctor (relevant lines):

Running: native (2.1.277)
Last update attempt: success → 2.1.277 (2026-09-18)
Managed settings (remote): not fetched — no usable credentials for the settings fetch
Organization policy: not fetched: no API key or claude.ai sign-in for the policy lookup
Remote Control: Not signed in to claude.ai / Sign-in is missing the user:profile scope
No installation issues found.

In-session messages seen over the course of the failure, in order:
- Please run /login · API Error: 401 OAuth access token has been revoked.
- Anthropic profile login expired · Run /login to use your claude.ai account instead, or re-authenticate the profile
- Not logged in · Please run /login
- ⚠ Remote managed settings failed to load (the profile credential could not authenticate the fetch)
- ⚠ 3 MCP servers need authentication · run /mcp
```

### Steps to Reproduce

Reproduce the stale-lock state directly:

1. `claude auth logout`
2. `touch ~/.claude/.storage-write.lock` (a regular file, simulating whatever left it behind; in my case it appeared around the auto-update to 2.1.277)
3. `claude auth login` and complete the browser flow → prints `Login successful.`
4. `claude auth status` → `"loggedIn": false`
5. `claude --debug`, run `/login`, then grep the newest `~/.claude/debug/*.txt` for `Failed to save OAuth tokens` → `ENOTDIR: not a directory, rmdir '.../.claude/.storage-write.lock'`

Fix/workaround:

6. `rm -f ~/.claude/.storage-write.lock`
7. `claude auth login` → `claude auth status` now shows `"loggedIn": true, "authMethod": "claude.ai"`

I did not determine what originally created the lock as a file. Timeline: everything worked before the 2026-09-18 auto-update to 2.1.277; first symptom was the 401 revoked-token error in an existing session immediately after.

### Claude Model

None

### Is this a regression?

Yes, this worked in a previous version

### Last Working Version

_No response_

### Claude Code Version

2.1.277 (Claude Code), native install, darwin-arm64, commit 97305f0832e6

### Platform

Anthropic API

### Operating System

macOS

### Terminal/Shell

Terminal.app (macOS)

### Additional Information

- Auth: claude.ai account (Max subscription), no ANTHROPIC_API_KEY, no CLAUDE_CONFIG_DIR, no apiKeyHelper, no ANTHROPIC_PROFILE.
- Keychain is the default login keychain and writes from the same shell succeed.
- Plugins enabled: vercel, posthog (claude-plugins-official), last30days.
- Existing issues searched: this is the macOS counterpart of #62600 ("Windows: stale lock directories block credential persistence after abnormal exit", closed as stale and locked, which asks for a new issue referencing it) and #71717. Same mechanism (mkdir-as-mutex lock left behind, /login reports success, credentials never written); the difference here is the stale `.storage-write.lock` was a regular file rather than a directory, so the failure surfaces as `ENOTDIR` on `rmdir`. Also likely related to #95386 and #87192.
- Suggestion: have `claude doctor` / `/doctor` check for a stale `*.lock` in the config dir, since it currently reports a clean install in this state.
- Candidate cause: a prior version was running in an active session when the auto-update to 2.1.277 landed, so a change in lock format between builds (file-based vs mkdir-based) is a likely way the lock ended up as a regular file. Nothing else writes to `~/.claude/` on this machine.

Contributor guide

No contributing guide indexed for this repository

Research direction

Reproduce with `touch ~/.claude/.storage-write.lock`, then run `claude auth login` and inspect the newest `~/.claude/debug/*.txt` for the token-save failure. Trace the credential save and lock cleanup used by `claude auth login` and in-session `/login`; done means a regular stale lock no longer prevents persistence, or the login surfaces the save error instead of reporting success.

Written by the indexing model from the issue text.

Assessment

Tech stack
cli, macos
Domain
authentication, cli, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.