fix(claudecode): pin CLAUDE_CONFIG_DIR to the per-run config dir, not empty #213

Merged
dominik.polakovics merged 1 commit from fix/claude-config-dir-empty-pin into main 2026-07-23 03:19:12 +02:00

Problem

Since #209 landed, chat messages "never reach" a running instance: the interface spawns claude, but every reply is answered with "Not logged in · Please run /login" — the instance is unauthenticated, so from the operator's side messages silently vanish.

Root cause

The injector pinned CLAUDE_CONFIG_DIR= (empty), relying on the compat §3a fact "an empty value behaves as unset" — which was live-probed on CLI 2.1.214 only. On the deployed 2.1.198 the resolution splits:

  • .claude.json → falls back to HOME (so the config-write probe passed)
  • credentials → the empty dir is joined into a relative ./.credentials.json (straced: openat(AT_FDCWD, ".credentials.json") = ENOENT)

The valid OAuth copy at <home>/.claude/.credentials.json was never read. Clean differential on 2.1.198: empty pin → "Not logged in"; var unset, same HOME → authenticated.

Fix

Pin CLAUDE_CONFIG_DIR=<home>/.claude explicitly. Set-outranks-HOME resolves identically on every probed CLI version, and it is the directory every adapter reader already resolves under (projects/, sessions/, commands/, .credentials.json). The one moved path: the trust/onboarding/oauthAccount seed now writes <home>/.claude/.claude.json (globalConfigUnder), and seedGlobalConfig creates the parent dir since SeedWorkspace runs before InjectCredentials in the launch order.

Verification

  • 2.1.198 end-to-end with the fixed binary: spawn → authenticated → chat reply answered (CANARY-OK) → chat view renders the round-trip; transcript and deep-link session registry both under the pinned dir.
  • 2.1.214 (npm musl build): compat live probe passes incl. the new nested-layout subtest; fake-token differential proves credential discovery under the pin ("could not be refreshed" = file found, vs "Not logged in" = the old bug).
  • compat.md §3a rewritten to document the empty-value version trap; the live probe now exercises the exact pin layout; inject tests gain a layout-consistency regression guard (pin dir == credential/transcript/registry dir, never empty).

Side finding (not fixed here, worth an issue): lab exits at startup when <state-dir>/agent.sock exceeds the ~108-char unix socket path limit (bind: invalid argument) — long state dirs break the #208 default agent socket.

🤖 Generated with Claude Code

## Problem Since #209 landed, chat messages "never reach" a running instance: the interface spawns claude, but every reply is answered with **"Not logged in · Please run /login"** — the instance is unauthenticated, so from the operator's side messages silently vanish. ## Root cause The injector pinned `CLAUDE_CONFIG_DIR=` (empty), relying on the compat §3a fact "an empty value behaves as unset" — which was live-probed on CLI **2.1.214** only. On the deployed **2.1.198** the resolution splits: - `.claude.json` → falls back to HOME (so the config-write probe passed) - **credentials** → the empty dir is joined into a **relative** `./.credentials.json` (straced: `openat(AT_FDCWD, ".credentials.json") = ENOENT`) The valid OAuth copy at `<home>/.claude/.credentials.json` was never read. Clean differential on 2.1.198: empty pin → "Not logged in"; var unset, same HOME → authenticated. ## Fix Pin `CLAUDE_CONFIG_DIR=<home>/.claude` **explicitly**. Set-outranks-HOME resolves identically on every probed CLI version, and it is the directory every adapter reader already resolves under (`projects/`, `sessions/`, `commands/`, `.credentials.json`). The one moved path: the trust/onboarding/oauthAccount seed now writes `<home>/.claude/.claude.json` (`globalConfigUnder`), and `seedGlobalConfig` creates the parent dir since SeedWorkspace runs before InjectCredentials in the launch order. ## Verification - **2.1.198 end-to-end** with the fixed binary: spawn → authenticated → chat reply answered (`CANARY-OK`) → chat view renders the round-trip; transcript and deep-link session registry both under the pinned dir. - **2.1.214** (npm musl build): compat live probe passes incl. the new nested-layout subtest; fake-token differential proves credential discovery under the pin ("could not be refreshed" = file found, vs "Not logged in" = the old bug). - compat.md §3a rewritten to document the empty-value version trap; the live probe now exercises the exact pin layout; inject tests gain a layout-consistency regression guard (pin dir == credential/transcript/registry dir, never empty). Side finding (not fixed here, worth an issue): `lab` exits at startup when `<state-dir>/agent.sock` exceeds the ~108-char unix socket path limit (`bind: invalid argument`) — long state dirs break the #208 default agent socket. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(claudecode): pin CLAUDE_CONFIG_DIR to the per-run config dir, not empty
All checks were successful
ci / native (pull_request) Successful in 8m8s
d4d31d4ce0
Every instance since #209 spawned unauthenticated on hosts running
claude < 2.1.214: the CLI answered each delivered chat reply with "Not
logged in · Please run /login", so messages looked like they never
reached the session.

Root cause: the injector pinned CLAUDE_CONFIG_DIR= (empty), relying on
"empty behaves as unset" — live-probed on 2.1.214 only. On the deployed
2.1.198 the resolution SPLITS: .claude.json falls back to HOME (so the
compat config-write probe passed), but the credentials path joins the
empty dir into a RELATIVE ./.credentials.json (straced live:
openat(AT_FDCWD, ".credentials.json") = ENOENT). The valid credential
copy at <home>/.claude/.credentials.json was never read.

Fix: pin CLAUDE_CONFIG_DIR=<home>/.claude explicitly — set-outranks-HOME
resolves identically on every probed version, and it is the directory
every adapter reader already uses (projects/, sessions/, commands/,
.credentials.json). The one moved path: the trust/onboarding/
oauthAccount seed now writes <home>/.claude/.claude.json
(globalConfigUnder), and seedGlobalConfig creates the parent dir since
SeedWorkspace runs before InjectCredentials.

Verified end-to-end on 2.1.198 (spawn → authenticated turn → chat view
renders the round-trip, transcript + session registry under the pinned
dir) and probed on 2.1.214 (compat live probe incl. the new nested-
layout subtest; fake-token credential-discovery differential). compat.md
§3a rewritten with the version-trap; inject tests gain a layout-
consistency guard (pin dir == credential/transcript/registry dir, never
empty).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dominik.polakovics deleted branch fix/claude-config-dir-empty-pin 2026-07-23 03:19:12 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab!213
No description provided.