Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2738a0c
Initial commit with task details
konard Jun 26, 2026
7c58988
chore(release): version packages
github-actions[bot] Jun 26, 2026
72bf8eb
fix(claude): keep OAuth token when post-login API probe fails
konard Jun 26, 2026
19bd8c3
Merge remote-tracking branch 'origin/main' into issue-439-c9a9c01e8b9b
konard Jun 26, 2026
09dc407
test(claude): verify docker-backed auth login
skulidropek Jun 29, 2026
a02936b
refactor(claude): extract docker oauth package
skulidropek Jun 29, 2026
bd54c54
chore(release): version packages
github-actions[bot] Jun 29, 2026
025b925
fix(auth): harden claude oauth login probe path
konard Jun 29, 2026
875fbd5
fix(app): keep controller compose lintable
konard Jun 29, 2026
de50520
fix(auth): address claude oauth review hardening
konard Jun 29, 2026
bbd1885
Merge remote-tracking branch 'origin/main' into issue-439-c9a9c01e8b9b
konard Jun 29, 2026
5ba9e51
fix(auth): persist claude oauth token atomically
konard Jun 29, 2026
88da062
fix(ci): avoid github api cache bust in project image
konard Jun 29, 2026
5dc13bc
fix(app): require gpu overlay regular file
konard Jun 29, 2026
06a4e2a
ci: retry bun install in setup action
konard Jun 29, 2026
bc1da97
fix(shell): isolate claude oauth env boundary
konard Jun 29, 2026
8abc88d
fix(shell): address coderabbit oauth review
konard Jun 29, 2026
aedf7d3
fix(test): isolate claude auth e2e token injection
konard Jun 29, 2026
c114e64
fix(auth): isolate Claude probes and state sync locks
skulidropek Jul 1, 2026
0d8a463
fix(auth): route Claude status through controller
skulidropek Jul 1, 2026
ae12482
fix(auth): show Claude account in status
skulidropek Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/fix-claude-auth-login-probe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@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`.

Controller startup now also rejects `DOCKER_GIT_CONTROLLER_GPU=all` when
`docker-compose.gpu.yml` exists as a directory instead of a regular file,
matching the extra compose overlay invariant before invoking Docker Compose.
30 changes: 29 additions & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,32 @@ runs:
run: npm install -g node-gyp
- name: Install dependencies
shell: bash
run: bun install --frozen-lockfile
run: |
run_bun_install() {
local timeout_seconds=$((20 * 60))
bun install --frozen-lockfile &
local install_pid="$!"
(
sleep "$timeout_seconds"
echo "bun install exceeded 20 minutes; terminating" >&2
kill "$install_pid" 2>/dev/null || true
) &
local timeout_pid="$!"
local status=0
wait "$install_pid" || status="$?"
kill "$timeout_pid" 2>/dev/null || true
wait "$timeout_pid" 2>/dev/null || true
Comment on lines +63 to +74

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

На timeout нужно завершать всю process group, а не только родительский PID.

На Lines 63-74 watchdog шлёт kill только в bun install. Если postinstall или другой lifecycle-скрипт породит дочерние процессы, они переживут retry и смогут оставить lock/сокеты/IO, а следующая попытка стартует поверх них. Здесь нужен запуск в отдельной process group и сигнал по всей группе.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/setup/action.yml around lines 63 - 74, Update the timeout
handling in the setup action so the watchdog kills the entire process group, not
just the `bun install` parent PID. In the `bun install --frozen-lockfile` block,
start the install in its own process group and make the timeout path send the
termination signal to that group, including any child processes spawned by
lifecycle scripts. Keep the existing `install_pid`, `timeout_pid`, and `wait`
flow in the action, but change the termination logic to target the full group.

return "$status"
}

for attempt in 1 2 3; do
if run_bun_install; then
exit 0
fi
if [[ "$attempt" == "3" ]]; then
echo "bun install failed after retries" >&2
exit 1
fi
echo "bun install attempt ${attempt} failed; retrying..." >&2
sleep $((attempt * 2))
done
Comment on lines +61 to +88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Ретраи здесь недостижимы в текущем workflow timeout.

На Line 62 одна попытка может ждать 20 минут, но .github/workflows/check.yml уже использует этот action в build job с timeout-minutes: 10. В этом job GitHub завершит весь job раньше первого watchdog/retry, так что новая логика не даст заявленного восстановления после transient install failures. Сведите watchdog ниже минимального job timeout или поднимите timeout job.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/setup/action.yml around lines 61 - 88, The retry logic in
run_bun_install is effectively unreachable because the surrounding build job can
time out before the first 20-minute watchdog finishes. Update the action’s
timeout handling so it fits within the job’s actual timeout in the check
workflow, either by reducing the bun install watchdog/retry window or by
aligning the job timeout higher; keep the fix centered around run_bun_install
and the retry loop in this action.

21 changes: 21 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,27 @@ jobs:
- name: Login context notice
run: bash scripts/e2e/login-context.sh

e2e-auth-claude-login:
name: E2E (Claude auth login)
runs-on: ubuntu-latest
timeout-minutes: 40
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
persist-credentials: false
submodules: true
- name: Install dependencies
uses: ./.github/actions/setup
- name: Free Docker disk
uses: ./.github/actions/free-docker-disk
- name: Docker info
run: docker version && docker compose version
- name: Claude auth login warning path
run: bash scripts/e2e/auth-claude-login.sh

e2e-runtime-volumes-ssh:
name: E2E (Runtime volumes + SSH)
runs-on: ubuntu-latest
Expand Down
19 changes: 17 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"workspaces": [
"packages/api",
"packages/app",
"packages/auth-oauth",
"packages/container",
"packages/docker-git-session-sync",
"packages/lib",
Expand All @@ -15,13 +16,13 @@
],
"scripts": {
"setup:pre-commit-hook": "bun scripts/setup-pre-commit-hook.js",
"build": "bun run --filter @prover-coder-ai/docker-git-session-sync build && bun run --filter @prover-coder-ai/docker-git-terminal build && bun run --filter @prover-coder-ai/docker-git build",
"build": "bun run --filter @prover-coder-ai/docker-git-auth-oauth build && bun run --filter @prover-coder-ai/docker-git-session-sync build && bun run --filter @prover-coder-ai/docker-git-terminal build && bun run --filter @prover-coder-ai/docker-git build",
"api:build": "bun run --filter @effect-template/api build",
"api:start": "bun run --filter @effect-template/api start",
"api:dev": "bun run --filter @effect-template/api dev",
"api:test": "bun run --filter @effect-template/api test",
"api:typecheck": "bun run --filter @effect-template/api typecheck",
"check": "bun run --filter @prover-coder-ai/docker-git-session-sync check && bun run --filter @prover-coder-ai/docker-git-terminal check && bun run --filter @prover-coder-ai/docker-git-openapi check && bun run --filter @prover-coder-ai/docker-git check && bun run --filter @effect-template/lib typecheck",
"check": "bun run --filter @prover-coder-ai/docker-git-auth-oauth check && bun run --filter @prover-coder-ai/docker-git-session-sync check && bun run --filter @prover-coder-ai/docker-git-terminal check && bun run --filter @prover-coder-ai/docker-git-openapi check && bun run --filter @prover-coder-ai/docker-git check && bun run --filter @effect-template/lib typecheck",
"check:dist-deps-prune": "bun node_modules/@prover-coder-ai/dist-deps-prune/dist/main.js scan --package ./packages/app/package.json --prune-dev true --silent",
"changeset": "changeset",
"changeset-publish": "bun -e \"if (!process.env.NPM_TOKEN) { console.log('Skipping publish: NPM_TOKEN is not set'); process.exit(0); }\" && changeset publish",
Expand Down Expand Up @@ -52,8 +53,8 @@
"lint:effect": "bun run --filter @prover-coder-ai/docker-git-session-sync lint:effect && bun run --filter @prover-coder-ai/docker-git-terminal lint:effect && bun run --filter @prover-coder-ai/docker-git lint:effect && bun run --filter @prover-coder-ai/docker-git-container lint:effect && bun run --filter @effect-template/lib lint:effect && bun run --filter @effect-template/api lint:effect",
"effect:skill:init": "git submodule update --init --checkout third_party/effect-ts-skills",
"effect:skill:check": "bun run effect:skill:init && bash .codex/skills/effect-ts-guide/scripts/run-effect-ts-check.sh packages/app/src/web/api-create-project.ts packages/app/src/web/api-database.ts packages/app/src/web/api-http.ts packages/app/src/web/api-prompts.ts packages/app/src/web/api-skills.ts packages/app/src/web/api-tasks.ts packages/openapi/src --profile strict",
"test": "bun run --filter @prover-coder-ai/docker-git-session-sync test && bun run --filter @prover-coder-ai/docker-git-terminal test && bun run --filter @prover-coder-ai/docker-git test && bun run --filter @effect-template/lib test",
"typecheck": "bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git-terminal typecheck && bun run --filter @prover-coder-ai/docker-git-openapi typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck",
"test": "bun run --filter @prover-coder-ai/docker-git-auth-oauth test && bun run --filter @prover-coder-ai/docker-git-session-sync test && bun run --filter @prover-coder-ai/docker-git-terminal test && bun run --filter @prover-coder-ai/docker-git test && bun run --filter @effect-template/lib test",
"typecheck": "bun run --filter @prover-coder-ai/docker-git-auth-oauth typecheck && bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git-terminal typecheck && bun run --filter @prover-coder-ai/docker-git-openapi typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck",
"start": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js"
},
"devDependencies": {
Expand Down
6 changes: 5 additions & 1 deletion packages/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ RUN set -eu; \
FROM controller-base AS workspace-deps

COPY package.json bun.lock bunfig.toml tsconfig.base.json tsconfig.json ./
RUN mkdir -p packages/api packages/app packages/container packages/docker-git-session-sync packages/lib packages/openapi packages/terminal
RUN mkdir -p packages/api packages/app packages/auth-oauth packages/container packages/docker-git-session-sync packages/lib packages/openapi packages/terminal
COPY packages/api/package.json ./packages/api/package.json
COPY packages/app/package.json ./packages/app/package.json
COPY packages/auth-oauth/package.json ./packages/auth-oauth/package.json
COPY packages/container/package.json ./packages/container/package.json
COPY packages/docker-git-session-sync/package.json ./packages/docker-git-session-sync/package.json
COPY packages/lib/package.json ./packages/lib/package.json
Expand All @@ -92,6 +93,7 @@ RUN set -eu; \
--silent \
--filter @effect-template/api \
--filter @effect-template/lib \
--filter @prover-coder-ai/docker-git-auth-oauth \
--filter @prover-coder-ai/docker-git-container \
--filter @prover-coder-ai/docker-git-terminal \
--filter @prover-coder-ai/docker-git-session-sync; then \
Expand All @@ -109,12 +111,14 @@ FROM workspace-deps AS workspace-static
COPY patches ./patches
COPY scripts ./scripts
COPY packages/container ./packages/container
COPY packages/auth-oauth ./packages/auth-oauth
COPY packages/docker-git-session-sync ./packages/docker-git-session-sync
COPY packages/lib ./packages/lib
COPY packages/terminal ./packages/terminal

RUN bun run --cwd packages/docker-git-session-sync build
RUN bun run --cwd packages/terminal build
RUN bun run --cwd packages/auth-oauth build
RUN bun run --cwd packages/container build
RUN bun run --cwd packages/lib build

Expand Down
9 changes: 9 additions & 0 deletions packages/api/src/api/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,15 @@ export type CodexAuthStatus = {
readonly account: string | null
}

export type ClaudeAuthStatus = {
readonly label: string
readonly message: string
readonly connected: boolean
readonly authPath: string
readonly account: string | null
readonly method: "none" | "oauth-token" | "claude-ai-session"
}

export type GrokAuthStatus = {
readonly label: string
readonly message: string
Expand Down
19 changes: 19 additions & 0 deletions packages/api/src/api/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,20 @@ export const CodexStatusResponseSchema = Schema.Struct({
status: CodexAuthStatusSchema
})

export const ClaudeAuthStatusSchema = Schema.Struct({
account: NullableStringSchema,
authPath: Schema.String,
connected: Schema.Boolean,
label: Schema.String,
message: Schema.String,
method: Schema.Literal("none", "oauth-token", "claude-ai-session")
})

export const ClaudeStatusResponseSchema = Schema.Struct({
ok: OptionalOkSchema,
status: ClaudeAuthStatusSchema
})

export const GrokAuthStatusSchema = Schema.Struct({
authPath: Schema.String,
connected: Schema.Boolean,
Expand Down Expand Up @@ -601,6 +615,11 @@ const AuthGroup = HttpApiGroup.make("auth")
.setUrlParams(QueryLabelSchema)
.addSuccess(CodexStatusResponseSchema)
)
.add(
endpoint.get("claudeStatus", "/auth/claude/status")
.setUrlParams(QueryLabelSchema)
.addSuccess(ClaudeStatusResponseSchema)
)
.add(
endpoint.post("githubLogin", "/auth/github/login")
.setPayload(GithubAuthLoginRequestSchema)
Expand Down
10 changes: 10 additions & 0 deletions packages/api/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
logoutGitAuth,
logoutGitlabAuth,
logoutGithubAuth,
readClaudeAuthStatus,
readCodexAuthStatus,
readGrokAuthStatus,
readGitAuthStatus,
Expand Down Expand Up @@ -1138,6 +1139,15 @@ export const makeRouter = () => {
return yield* _(jsonResponse({ status }, 200))
}).pipe(Effect.catchAll(errorResponse))
),
HttpRouter.get(
"/auth/claude/status",
Effect.gen(function*(_) {
const request = yield* _(HttpServerRequest.HttpServerRequest)
const label = new URL(request.url, "http://localhost").searchParams.get("label")
const status = yield* _(readClaudeAuthStatus(label))
return yield* _(jsonResponse({ status }, 200))
}).pipe(Effect.catchAll(errorResponse))
),
Comment on lines +1142 to +1150

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Проверить, есть ли middleware авторизации, оборачивающий /auth маршруты
rg -n "HttpMiddleware|Authorization|authenticate" packages/api/src/http.ts packages/api/src -g '!**/*.test.ts'

Repository: ProverCoderAI/docker-git

Length of output: 1743


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== program.ts outline ==\n'
ast-grep outline packages/api/src/program.ts --view expanded || true

printf '\n== http.ts outline (around auth routes) ==\n'
ast-grep outline packages/api/src/http.ts --view expanded | sed -n '1,220p' || true

printf '\n== auth status routes and middleware references ==\n'
rg -n '"/auth/[^"]+/status"|HttpMiddleware|withMiddlewares|middleware|Auth' packages/api/src/http.ts packages/api/src/program.ts

Repository: ProverCoderAI/docker-git

Length of output: 16514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== program.ts (lines 1-120) ==\n'
sed -n '1,120p' packages/api/src/program.ts

printf '\n== http.ts (routes around /auth and wrapper) ==\n'
sed -n '1100,1338p' packages/api/src/http.ts

printf '\n== readClaudeAuthStatus definition ==\n'
rg -n "readClaudeAuthStatus|type .*AuthStatus|account" packages/api/src/services packages/api/src -g '!**/*.test.ts'

Repository: ProverCoderAI/docker-git

Length of output: 40037


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== packages/api/src/services/auth.ts (480-540, 708-720) ==\n'
sed -n '480,540p' packages/api/src/services/auth.ts
printf '\n---\n'
sed -n '708,720p' packages/api/src/services/auth.ts

printf '\n== packages/api/src/api/contracts.ts (290-315) ==\n'
sed -n '290,315p' packages/api/src/api/contracts.ts

printf '\n== route/app middleware search ==\n'
rg -n 'HttpMiddleware|serve\\(|withMiddleware|withMiddlewares|HttpServer\\.serve|auth.*middleware|require.*auth|ApiAuthRequiredError' packages/api/src

Repository: ProverCoderAI/docker-git

Length of output: 3284


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== auth / middleware search ==\n'
rg -n 'HttpMiddleware|HttpRouter\.(use|mount|group|prefix|prefixPath)|withMiddlewares|withMiddleware|authorize|authenticated|ApiAuthRequiredError' packages/api/src

printf '\n== makeRouter head and app assembly around withCoreRoutes ==\n'
sed -n '1040,1360p' packages/api/src/http.ts

printf '\n== Claude account extraction ==\n'
sed -n '600,705p' packages/api/src/services/auth.ts

Repository: ProverCoderAI/docker-git

Length of output: 22509


Закрыть /auth/*/status от публичного доступа или убрать account из ответа. В packages/api/src/http.ts:1142-1150 эти маршруты живут без отдельного auth-guard, а readClaudeAuthStatus() может вернуть emailAddress/displayName из локальных credential-файлов; при веб-старте на 0.0.0.0 это раскрывает PII по LAN.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/src/http.ts` around lines 1142 - 1150, The `/auth/claude/status`
handler currently exposes auth status without a guard, and
`readClaudeAuthStatus()` can leak `emailAddress`/`displayName` from local
credentials. Protect this route with the same auth check used for other
sensitive `/auth/*` endpoints in `HttpRouter.get`, or remove `account` fields
from the payload returned via `jsonResponse`. Use `readClaudeAuthStatus` and the
existing route registration in `packages/api/src/http.ts` to keep the fix
localized.

Source: Path instructions

HttpRouter.get(
"/auth/menu",
Effect.gen(function*(_) {
Expand Down
Loading
Loading