From 2738a0cadfc68b47838d6e694b6268c86d1267a3 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 26 Jun 2026 08:24:20 +0000 Subject: [PATCH 1/3] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/ProverCoderAI/docker-git/issues/439 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..ee4d936c --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-06-26T08:24:20.247Z for PR creation at branch issue-439-c9a9c01e8b9b for issue https://github.com/ProverCoderAI/docker-git/issues/439 \ No newline at end of file From 7c58988d148df9b2177e01824f1004b812358588 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 08:33:07 +0000 Subject: [PATCH 2/3] chore(release): version packages --- packages/app/CHANGELOG.md | 9 +++++++++ packages/app/package.json | 2 +- packages/docker-git-session-sync/CHANGELOG.md | 6 ++++++ packages/docker-git-session-sync/package.json | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 2cc86b25..d13ddbb3 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,14 @@ # @prover-coder-ai/docker-git +## 1.3.14 + +### Patch Changes + +- chore: automated version bump + +- Updated dependencies []: + - @prover-coder-ai/docker-git-session-sync@1.0.70 + ## 1.3.13 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 2caac76a..07ed7322 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@prover-coder-ai/docker-git", - "version": "1.3.13", + "version": "1.3.14", "description": "docker-git Bun and Gridland CLI plus browser frontend", "main": "dist/src/docker-git/main.js", "bin": { diff --git a/packages/docker-git-session-sync/CHANGELOG.md b/packages/docker-git-session-sync/CHANGELOG.md index 424da984..0b49da7b 100644 --- a/packages/docker-git-session-sync/CHANGELOG.md +++ b/packages/docker-git-session-sync/CHANGELOG.md @@ -1,5 +1,11 @@ # @prover-coder-ai/docker-git-session-sync +## 1.0.70 + +### Patch Changes + +- chore: automated version bump + ## 1.0.69 ### Patch Changes diff --git a/packages/docker-git-session-sync/package.json b/packages/docker-git-session-sync/package.json index 44aebcee..f8caac85 100644 --- a/packages/docker-git-session-sync/package.json +++ b/packages/docker-git-session-sync/package.json @@ -1,6 +1,6 @@ { "name": "@prover-coder-ai/docker-git-session-sync", - "version": "1.0.69", + "version": "1.0.70", "description": "Standalone docker-git AI agent session synchronization tool", "main": "dist/docker-git-session-sync.js", "bin": { From 72bf8eb51db6747358832d032b5e7e05ae2509ec Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 26 Jun 2026 08:34:10 +0000 Subject: [PATCH 3/3] fix(claude): keep OAuth token when post-login API probe fails docker-git auth claude login created and persisted the OAuth token, then ran a 'claude -p ping' probe and hard-failed (exit 1) on any non-zero probe exit, discarding an otherwise successful login. Transient probe failures (network, rate limit, token propagation delay) must not invalidate a saved token. The probe failure is now logged as a warning, mirroring authClaudeStatus. Adds a regression test asserting the token is persisted even when the probe returns non-zero. Fixes #439 --- .changeset/fix-claude-auth-login-probe.md | 15 ++ .gitkeep | 1 - packages/lib/src/usecases/auth-claude.ts | 15 +- .../tests/usecases/auth-claude-login.test.ts | 162 ++++++++++++++++++ 4 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 .changeset/fix-claude-auth-login-probe.md delete mode 100644 .gitkeep create mode 100644 packages/lib/tests/usecases/auth-claude-login.test.ts diff --git a/.changeset/fix-claude-auth-login-probe.md b/.changeset/fix-claude-auth-login-probe.md new file mode 100644 index 00000000..5ba46e72 --- /dev/null +++ b/.changeset/fix-claude-auth-login-probe.md @@ -0,0 +1,15 @@ +--- +"@prover-coder-ai/docker-git": patch +--- + +Fix `docker-git auth claude login` failing after a successful OAuth login. + +After `claude setup-token` created and persisted the OAuth token, the login +command ran a verification probe (`claude -p ping`) and treated any non-zero +exit as a hard failure, exiting with code 1 even though the token was already +saved. A transient probe failure (network hiccup, rate limit, or token +propagation delay) would therefore discard an otherwise successful login. + +The probe failure is now reported as a warning instead of an error, mirroring +`docker-git auth claude status`. The token is kept, and the user is advised to +re-check connectivity later with `docker-git auth claude status`. diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index ee4d936c..00000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-06-26T08:24:20.247Z for PR creation at branch issue-439-c9a9c01e8b9b for issue https://github.com/ProverCoderAI/docker-git/issues/439 \ No newline at end of file diff --git a/packages/lib/src/usecases/auth-claude.ts b/packages/lib/src/usecases/auth-claude.ts index a9907c0e..e143b7d9 100644 --- a/packages/lib/src/usecases/auth-claude.ts +++ b/packages/lib/src/usecases/auth-claude.ts @@ -268,14 +268,19 @@ export const authClaudeLogin = ( yield* _(fs.writeFileString(claudeOauthTokenPath(accountPath), `${token}\n`)) yield* _(fs.chmod(claudeOauthTokenPath(accountPath), 0o600), Effect.orElseSucceed(() => void 0)) yield* _(resolveClaudeAuthMethod(fs, path, accountPath)) + // CHANGE: treat a failing post-login API probe as a warning instead of a hard error + // WHY: the OAuth token is already created and persisted; a transient probe failure + // (network hiccup, rate limit, token propagation delay) must not discard a + // successful login. Mirrors authClaudeStatus, which only warns on probe failure. + // REF: issue-439 + // SOURCE: n/a const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath, token)) if (probeExitCode !== 0) { yield* _( - Effect.fail( - new CommandFailedError({ - command: "claude setup-token", - exitCode: probeExitCode - }) + Effect.logWarning( + `Claude OAuth token saved (${accountLabel}), but the API probe failed (exit=${probeExitCode}). ` + + `The token may need a moment to activate, or there was a transient network issue. ` + + `Verify later with 'docker-git auth claude status'.` ) ) } diff --git a/packages/lib/tests/usecases/auth-claude-login.test.ts b/packages/lib/tests/usecases/auth-claude-login.test.ts new file mode 100644 index 00000000..8d3e31d2 --- /dev/null +++ b/packages/lib/tests/usecases/auth-claude-login.test.ts @@ -0,0 +1,162 @@ +import * as Command from "@effect/platform/Command" +import * as CommandExecutor from "@effect/platform/CommandExecutor" +import * as FileSystem from "@effect/platform/FileSystem" +import * as Path from "@effect/platform/Path" +import { NodeContext } from "@effect/platform-node" +import { describe, expect, it } from "@effect/vitest" +import { Effect } from "effect" +import * as Inspectable from "effect/Inspectable" +import * as Sink from "effect/Sink" +import * as Stream from "effect/Stream" + +import { authClaudeLogin } from "../../src/usecases/auth-claude.js" + +const encode = (value: string): Uint8Array => new TextEncoder().encode(value) + +const oauthToken = "sk-ant-oat01-EXAMPLE0123456789abcdef" + +// Mirrors the real `claude setup-token` output that the OAuth parser scans for. +const setupTokenOutput = (token: string): string => + [ + "Welcome to Claude Code", + "", + " ✓ Long-lived authentication token created successfully!", + "", + " Your OAuth token (valid for 1 year):", + "", + ` ${token}`, + "", + " Store this token securely. You won't be able to see it again." + ].join("\n") + +const isSetupToken = (args: ReadonlyArray): boolean => args.includes("setup-token") +const isPingProbe = (args: ReadonlyArray): boolean => args.includes("-p") && args.includes("ping") + +// CHANGE: fake docker executor that captures a setup-token and lets the ping probe fail +// WHY: reproduce issue-439 where a successful OAuth login was discarded by a failing probe +// REF: issue-439 +const makeFakeExecutor = ( + token: string, + pingExitCode: number +): CommandExecutor.CommandExecutor => { + const start = (command: Command.Command): Effect.Effect => + Effect.sync(() => { + const flattened = Command.flatten(command) + const invocation = flattened[flattened.length - 1]! + const args = invocation.args + + const stdoutText = isSetupToken(args) ? setupTokenOutput(token) : "" + const exitCode = isPingProbe(args) ? pingExitCode : 0 + const stdout = stdoutText.length === 0 ? Stream.empty : Stream.succeed(encode(stdoutText)) + + const process: CommandExecutor.Process = { + [CommandExecutor.ProcessTypeId]: CommandExecutor.ProcessTypeId, + pid: CommandExecutor.ProcessId(1), + exitCode: Effect.succeed(CommandExecutor.ExitCode(exitCode)), + isRunning: Effect.succeed(false), + kill: (_signal) => Effect.void, + stderr: Stream.empty, + stdin: Sink.drain, + stdout, + toJSON: () => ({ _tag: "ClaudeLoginTestProcess", command: invocation.command, args }), + [Inspectable.NodeInspectSymbol]: () => ({ + _tag: "ClaudeLoginTestProcess", + command: invocation.command, + args + }), + toString: () => `[ClaudeLoginTestProcess ${invocation.command}]` + } + + return process + }) + + return CommandExecutor.makeExecutor(start) +} + +const withTempDir = ( + use: (tempDir: string) => Effect.Effect +): Effect.Effect => + Effect.scoped( + Effect.gen(function*(_) { + const fs = yield* _(FileSystem.FileSystem) + const tempDir = yield* _(fs.makeTempDirectoryScoped({ prefix: "docker-git-auth-claude-" })) + return yield* _(use(tempDir)) + }) + ) + +const withPatchedEnv = ( + patch: Readonly>, + effect: Effect.Effect +): Effect.Effect => + Effect.acquireUseRelease( + Effect.sync(() => { + const previous = new Map() + for (const [key, value] of Object.entries(patch)) { + previous.set(key, process.env[key]) + if (value === undefined) { + delete process.env[key] + } else { + process.env[key] = value + } + } + return previous + }), + () => effect, + (previous) => + Effect.sync(() => { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key] + } else { + process.env[key] = value + } + } + }) + ) + +const runLoginAndReadToken = ( + root: string, + pingExitCode: number +): Effect.Effect => + Effect.gen(function*(_) { + const fs = yield* _(FileSystem.FileSystem) + const path = yield* _(Path.Path) + const claudeAuthPath = path.join(root, ".docker-git/.orch/auth/claude") + + yield* _( + authClaudeLogin({ + _tag: "AuthClaudeLogin", + label: null, + claudeAuthPath + }).pipe( + Effect.provideService(CommandExecutor.CommandExecutor, makeFakeExecutor(oauthToken, pingExitCode)) + ) + ) + + return yield* _(fs.readFileString(path.join(claudeAuthPath, "default", ".oauth-token"))) + }) + +describe("authClaudeLogin", () => { + // Regression for issue-439: a non-zero probe exit must not discard a created token. + it.effect("persists the OAuth token even when the post-login API probe fails", () => + withTempDir((root) => + withPatchedEnv( + { HOME: root, DOCKER_GIT_STATE_AUTO_SYNC: "0", DOCKER_GIT_PROJECTS_ROOT: undefined }, + Effect.gen(function*(_) { + const persisted = yield* _(runLoginAndReadToken(root, 7)) + expect(persisted.trim()).toBe(oauthToken) + }) + ) + ).pipe(Effect.provide(NodeContext.layer))) + + it.effect("persists the OAuth token when the post-login API probe succeeds", () => + withTempDir((root) => + withPatchedEnv( + { HOME: root, DOCKER_GIT_STATE_AUTO_SYNC: "0", DOCKER_GIT_PROJECTS_ROOT: undefined }, + Effect.gen(function*(_) { + const persisted = yield* _(runLoginAndReadToken(root, 0)) + expect(persisted.trim()).toBe(oauthToken) + }) + ) + ).pipe(Effect.provide(NodeContext.layer))) +})