Skip to content

Commit 810530c

Browse files
authored
Merge pull request #113 from ProverCoderAI/issue-108
fix: Claude auth + permissions parity in docker-git containers
2 parents ce3538a + 1384db8 commit 810530c

File tree

11 files changed

+712
-286
lines changed

11 files changed

+712
-286
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ This avoids `refresh_token` rotation issues that can happen when copying `auth.j
115115
Disable sharing (per-project auth):
116116
- Set `CODEX_SHARE_AUTH=0` in `.orch/env/project.env`.
117117

118+
## Claude Code Defaults
119+
120+
On container start, docker-git syncs Claude Code user settings under `$CLAUDE_CONFIG_DIR/settings.json`:
121+
- `permissions.defaultMode = "bypassPermissions"` so local disposable containers behave like docker-git Codex containers (no permission prompts).
122+
- Existing unrelated Claude settings are preserved.
123+
118124
## Playwright MCP (Chromium Sidecar)
119125

120126
Enable during create/clone:

packages/app/tests/docker-git/entrypoint-auth.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,20 @@ describe("renderEntrypoint auth bridge", () => {
3939
expect(entrypoint).toContain("CLAUDE_CONFIG_DIR=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"")
4040
expect(entrypoint).toContain("docker_git_ensure_claude_cli()")
4141
expect(entrypoint).toContain("claude cli.js not found under npm global root; skip shim restore")
42+
expect(entrypoint).toContain("CLAUDE_PERMISSION_SETTINGS_FILE=\"$CLAUDE_CONFIG_DIR/settings.json\"")
43+
expect(entrypoint).toContain("docker_git_sync_claude_permissions()")
44+
expect(entrypoint).toContain(
45+
"const currentPermissions = isRecord(settings.permissions) ? settings.permissions : {}"
46+
)
47+
expect(entrypoint).toContain("defaultMode: \"bypassPermissions\"")
48+
expect(entrypoint).toContain("CLAUDE_TOKEN_FILE=\"$CLAUDE_CONFIG_DIR/.oauth-token\"")
4249
expect(entrypoint).toContain("CLAUDE_CREDENTIALS_FILE=\"$CLAUDE_CONFIG_DIR/.credentials.json\"")
43-
expect(entrypoint).toContain("if [[ -s \"$CLAUDE_CREDENTIALS_FILE\" ]]; then")
50+
expect(entrypoint).toContain("CLAUDE_NESTED_CREDENTIALS_FILE=\"$CLAUDE_CONFIG_DIR/.claude/.credentials.json\"")
51+
expect(entrypoint).toContain("docker_git_prepare_claude_auth_mode()")
52+
expect(entrypoint).toContain(
53+
"rm -f \"$CLAUDE_CREDENTIALS_FILE\" \"$CLAUDE_NESTED_CREDENTIALS_FILE\" \"$CLAUDE_HOME_DIR/.credentials.json\" || true"
54+
)
55+
expect(entrypoint).toContain("if [[ ! -s \"$CLAUDE_TOKEN_FILE\" ]]; then")
4456
expect(entrypoint).toContain("CLAUDE_SETTINGS_FILE=\"${CLAUDE_HOME_JSON:-$CLAUDE_CONFIG_DIR/.claude.json}\"")
4557
expect(entrypoint).toContain("nextServers.playwright = {")
4658
expect(entrypoint).toContain("command: \"docker-git-playwright-mcp\"")
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import type { TemplateConfig } from "../domain.js"
2+
3+
const entrypointClaudeGlobalPromptTemplate = String
4+
.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code)
5+
CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md"
6+
CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}"
7+
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository"
8+
REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}"
9+
REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}"
10+
11+
if [[ "$REPO_REF_VALUE" == issue-* ]]; then
12+
ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')"
13+
ISSUE_URL_VALUE=""
14+
if [[ "$REPO_URL_VALUE" == https://github.com/* ]]; then
15+
ISSUE_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
16+
if [[ -n "$ISSUE_REPO_VALUE" ]]; then
17+
ISSUE_URL_VALUE="https://github.com/$ISSUE_REPO_VALUE/issues/$ISSUE_ID_VALUE"
18+
fi
19+
fi
20+
if [[ -n "$ISSUE_URL_VALUE" ]]; then
21+
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE ($ISSUE_URL_VALUE)"
22+
else
23+
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE"
24+
fi
25+
elif [[ "$REPO_REF_VALUE" == refs/pull/*/head ]]; then
26+
PR_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
27+
PR_URL_VALUE=""
28+
if [[ "$REPO_URL_VALUE" == https://github.com/* && -n "$PR_ID_VALUE" ]]; then
29+
PR_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
30+
if [[ -n "$PR_REPO_VALUE" ]]; then
31+
PR_URL_VALUE="https://github.com/$PR_REPO_VALUE/pull/$PR_ID_VALUE"
32+
fi
33+
fi
34+
if [[ -n "$PR_ID_VALUE" && -n "$PR_URL_VALUE" ]]; then
35+
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE ($PR_URL_VALUE)"
36+
elif [[ -n "$PR_ID_VALUE" ]]; then
37+
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE"
38+
else
39+
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF_VALUE)"
40+
fi
41+
fi
42+
43+
if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then
44+
mkdir -p "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")"
45+
chown 1000:1000 "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")" 2>/dev/null || true
46+
if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^<!-- docker-git-managed:claude-md -->$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then
47+
cat <<EOF > "$CLAUDE_GLOBAL_PROMPT_FILE"
48+
<!-- docker-git-managed:claude-md -->
49+
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, claude, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
50+
Рабочая папка проекта (git clone): __TARGET_DIR__
51+
Доступные workspace пути: __TARGET_DIR__
52+
$CLAUDE_WORKSPACE_CONTEXT
53+
Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
54+
Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
55+
Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
56+
<!-- /docker-git-managed:claude-md -->
57+
EOF
58+
chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
59+
chown 1000:1000 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
60+
fi
61+
fi
62+
63+
export CLAUDE_AUTO_SYSTEM_PROMPT`
64+
65+
const escapeForDoubleQuotes = (value: string): string => {
66+
const backslash = String.fromCodePoint(92)
67+
const quote = String.fromCodePoint(34)
68+
const escapedBackslash = `${backslash}${backslash}`
69+
const escapedQuote = `${backslash}${quote}`
70+
return value
71+
.replaceAll(backslash, escapedBackslash)
72+
.replaceAll(quote, escapedQuote)
73+
}
74+
75+
export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string =>
76+
entrypointClaudeGlobalPromptTemplate
77+
.replaceAll("__TARGET_DIR__", config.targetDir)
78+
.replaceAll("__SSH_USER__", config.sshUser)
79+
.replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes(config.repoRef))
80+
.replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes(config.repoUrl))
81+
82+
export const renderClaudeWrapperSetup = (): string =>
83+
String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude"
84+
if command -v claude >/dev/null 2>&1; then
85+
CURRENT_CLAUDE_BIN="$(command -v claude)"
86+
CLAUDE_REAL_DIR="$(dirname "$CURRENT_CLAUDE_BIN")"
87+
CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
88+
89+
# If a wrapper already exists but points to a missing real binary, recover from /usr/bin.
90+
if [[ "$CURRENT_CLAUDE_BIN" == "$CLAUDE_WRAPPER_BIN" && ! -e "$CLAUDE_REAL_BIN" && -x "/usr/bin/claude" ]]; then
91+
CURRENT_CLAUDE_BIN="/usr/bin/claude"
92+
CLAUDE_REAL_DIR="/usr/bin"
93+
CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
94+
fi
95+
96+
# Keep the "real" binary in the same directory as the original command to preserve relative symlinks.
97+
if [[ "$CURRENT_CLAUDE_BIN" != "$CLAUDE_REAL_BIN" && ! -e "$CLAUDE_REAL_BIN" ]]; then
98+
mv "$CURRENT_CLAUDE_BIN" "$CLAUDE_REAL_BIN"
99+
fi
100+
if [[ -e "$CLAUDE_REAL_BIN" ]]; then
101+
cat <<'EOF' > "$CLAUDE_WRAPPER_BIN"
102+
#!/usr/bin/env bash
103+
set -euo pipefail
104+
105+
CLAUDE_REAL_BIN="__CLAUDE_REAL_BIN__"
106+
CLAUDE_CONFIG_DIR="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}"
107+
CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token"
108+
109+
if [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
110+
CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")"
111+
export CLAUDE_CODE_OAUTH_TOKEN
112+
else
113+
unset CLAUDE_CODE_OAUTH_TOKEN || true
114+
fi
115+
116+
exec "$CLAUDE_REAL_BIN" "$@"
117+
EOF
118+
sed -i "s#__CLAUDE_REAL_BIN__#$CLAUDE_REAL_BIN#g" "$CLAUDE_WRAPPER_BIN" || true
119+
chmod 0755 "$CLAUDE_WRAPPER_BIN" || true
120+
fi
121+
fi`

0 commit comments

Comments
 (0)