Skip to content

Commit fd104d8

Browse files
committed
fix(auto): align agent launch with container shell
1 parent 4dcce61 commit fd104d8

5 files changed

Lines changed: 96 additions & 16 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ describe("renderEntrypoint auth bridge", () => {
6868
expect(entrypoint).toContain(
6969
"docker_git_link_claude_file \"$CLAUDE_CONFIG_DIR/.claude.json\" \"$CLAUDE_HOME_JSON\""
7070
)
71+
expect(entrypoint).toContain("su - dev -s /bin/bash -c \"bash -lc")
72+
expect(entrypoint).toContain(". /etc/profile 2>/dev/null || true;")
73+
expect(entrypoint).toContain(String.raw`. \"$AGENT_ENV_FILE\" 2>/dev/null || true;`)
74+
expect(entrypoint).toContain(
75+
String.raw`claude --dangerously-skip-permissions -p \"\$(cat \"$AGENT_PROMPT_FILE\")\"`
76+
)
77+
expect(entrypoint).toContain(String.raw`codex exec \"\$(cat \"$AGENT_PROMPT_FILE\")\"`)
78+
expect(entrypoint).not.toContain("codex --approval-mode full-auto")
7179
expect(entrypoint).toContain("CLAUDE_GLOBAL_PROMPT_FILE=\"/home/dev/.claude/CLAUDE.md\"")
7280
expect(entrypoint).toContain("CLAUDE_AUTO_SYSTEM_PROMPT=\"${CLAUDE_AUTO_SYSTEM_PROMPT:-1}\"")
7381
expect(entrypoint).toContain("docker-git-managed:claude-md")

packages/lib/src/core/templates-entrypoint/agent.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,14 @@ fi`
4747

4848
const renderAgentPromptCommand = (mode: "claude" | "codex"): string =>
4949
mode === "claude"
50-
? String.raw`claude --dangerously-skip-permissions -p \"\$(cat $AGENT_PROMPT_FILE)\"`
51-
: String.raw`codex --approval-mode full-auto \"\$(cat $AGENT_PROMPT_FILE)\"`
50+
? String.raw`claude --dangerously-skip-permissions -p \"\$(cat \"$AGENT_PROMPT_FILE\")\"`
51+
: String.raw`codex exec \"\$(cat \"$AGENT_PROMPT_FILE\")\"`
52+
53+
const renderAgentAutoLaunchCommand = (
54+
config: TemplateConfig,
55+
mode: "claude" | "codex"
56+
): string =>
57+
String.raw`su - ${config.sshUser} -s /bin/bash -c "bash -lc '. /etc/profile 2>/dev/null || true; . \"$AGENT_ENV_FILE\" 2>/dev/null || true; cd \"$TARGET_DIR\" && ${renderAgentPromptCommand(mode)}'"`
5258

5359
const renderAgentModeBlock = (
5460
config: TemplateConfig,
@@ -60,8 +66,7 @@ const renderAgentModeBlock = (
6066
return String.raw`"${mode}")
6167
echo "${startMessage}"
6268
if [[ -n "$AGENT_PROMPT" ]]; then
63-
if su - ${config.sshUser} \
64-
-c ". /run/docker-git/agent-env.sh 2>/dev/null; cd '$TARGET_DIR' && ${renderAgentPromptCommand(mode)}"; then
69+
if ${renderAgentAutoLaunchCommand(config, mode)}; then
6570
AGENT_OK=1
6671
fi
6772
else

packages/lib/src/core/templates/docker-compose.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ const renderAgentAutoEnv = (agentAuto: boolean | undefined): string =>
4545
? ` AGENT_AUTO: "1"\n`
4646
: ""
4747

48+
const renderProjectsRootHostMount = (projectsRoot: string): string =>
49+
`\${DOCKER_GIT_PROJECTS_ROOT_HOST:-${projectsRoot}}`
50+
51+
const renderSharedCodexHostMount = (projectsRoot: string): string =>
52+
`\${DOCKER_GIT_PROJECTS_ROOT_HOST:-${projectsRoot}}/.orch/auth/codex`
53+
4854
const buildPlaywrightFragments = (
4955
config: TemplateConfig,
5056
networkName: string
@@ -126,10 +132,10 @@ ${fragments.maybePlaywrightEnv}${fragments.maybeDependsOn} env_file:
126132
- "127.0.0.1:${config.sshPort}:22"
127133
volumes:
128134
- ${config.volumeName}:/home/${config.sshUser}
129-
- ${config.dockerGitPath}:/home/${config.sshUser}/.docker-git
135+
- ${renderProjectsRootHostMount(config.dockerGitPath)}:/home/${config.sshUser}/.docker-git
130136
- ${config.authorizedKeysPath}:/authorized_keys:ro
131137
- ${config.codexAuthPath}:${config.codexHome}
132-
- ${config.codexSharedAuthPath}:${config.codexHome}-shared
138+
- ${renderSharedCodexHostMount(config.dockerGitPath)}:${config.codexHome}-shared
133139
- /var/run/docker.sock:/var/run/docker.sock
134140
networks:
135141
- ${fragments.networkName}

packages/lib/src/shell/docker.ts

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { PlatformError } from "@effect/platform/Error"
55
import { Duration, Effect, pipe, Schedule } from "effect"
66

77
import { runCommandCapture, runCommandExitCode, runCommandWithExitCodes } from "./command-runner.js"
8+
import { resolveDockerVolumeHostPath } from "./docker-auth.js"
89
import { CommandFailedError, DockerCommandError } from "./errors.js"
910

1011
export { classifyDockerAccessIssue, ensureDockerDaemonAccess } from "./docker-daemon-access.js"
@@ -16,6 +17,46 @@ const composeSpec = (cwd: string, args: ReadonlyArray<string>) => ({
1617
args: ["compose", "--ansi", "never", "--progress", "plain", ...args]
1718
})
1819

20+
const resolveEnvValue = (key: string): string | null => {
21+
const value = process.env[key]?.trim()
22+
return value && value.length > 0 ? value : null
23+
}
24+
25+
const trimTrailingSlash = (value: string): string => {
26+
let end = value.length
27+
while (end > 0) {
28+
const char = value[end - 1]
29+
if (char !== "/" && char !== "\\") {
30+
break
31+
}
32+
end -= 1
33+
}
34+
return value.slice(0, end)
35+
}
36+
37+
const resolveProjectsRootCandidate = (): string | null => {
38+
const explicit = resolveEnvValue("DOCKER_GIT_PROJECTS_ROOT")
39+
if (explicit !== null) {
40+
return explicit
41+
}
42+
43+
const home = resolveEnvValue("HOME") ?? resolveEnvValue("USERPROFILE")
44+
return home === null ? null : `${trimTrailingSlash(home)}/.docker-git`
45+
}
46+
47+
const resolveComposeEnv = (
48+
cwd: string
49+
): Effect.Effect<Readonly<Record<string, string>>, never, CommandExecutor.CommandExecutor> =>
50+
Effect.gen(function*(_) {
51+
const projectsRoot = resolveProjectsRootCandidate()
52+
if (projectsRoot === null) {
53+
return {}
54+
}
55+
56+
const remappedProjectsRoot = yield* _(resolveDockerVolumeHostPath(cwd, projectsRoot))
57+
return remappedProjectsRoot === projectsRoot ? {} : { DOCKER_GIT_PROJECTS_ROOT_HOST: remappedProjectsRoot }
58+
})
59+
1960
const parseInspectNetworkEntry = (line: string): ReadonlyArray<readonly [string, string]> => {
2061
const idx = line.indexOf("=")
2162
if (idx <= 0) {
@@ -35,22 +76,38 @@ const runCompose = (
3576
args: ReadonlyArray<string>,
3677
okExitCodes: ReadonlyArray<number>
3778
): Effect.Effect<void, DockerCommandError | PlatformError, CommandExecutor.CommandExecutor> =>
38-
runCommandWithExitCodes(
39-
composeSpec(cwd, args),
40-
okExitCodes,
41-
(exitCode) => new DockerCommandError({ exitCode })
42-
)
79+
Effect.gen(function*(_) {
80+
const env = yield* _(resolveComposeEnv(cwd))
81+
yield* _(
82+
runCommandWithExitCodes(
83+
{
84+
...composeSpec(cwd, args),
85+
...(Object.keys(env).length > 0 ? { env } : {})
86+
},
87+
okExitCodes,
88+
(exitCode) => new DockerCommandError({ exitCode })
89+
)
90+
)
91+
})
4392

4493
const runComposeCapture = (
4594
cwd: string,
4695
args: ReadonlyArray<string>,
4796
okExitCodes: ReadonlyArray<number>
4897
): Effect.Effect<string, DockerCommandError | PlatformError, CommandExecutor.CommandExecutor> =>
49-
runCommandCapture(
50-
composeSpec(cwd, args),
51-
okExitCodes,
52-
(exitCode) => new DockerCommandError({ exitCode })
53-
)
98+
Effect.gen(function*(_) {
99+
const env = yield* _(resolveComposeEnv(cwd))
100+
return yield* _(
101+
runCommandCapture(
102+
{
103+
...composeSpec(cwd, args),
104+
...(Object.keys(env).length > 0 ? { env } : {})
105+
},
106+
okExitCodes,
107+
(exitCode) => new DockerCommandError({ exitCode })
108+
)
109+
)
110+
})
54111

55112
const dockerComposeUpRetrySchedule = Schedule.addDelay(
56113
Schedule.recurs(2),

packages/lib/tests/usecases/prepare-files.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ describe("prepareProjectFiles", () => {
137137
expect(entrypoint).toContain('OPENCODE_DATA_DIR="/home/dev/.local/share/opencode"')
138138
expect(entrypoint).toContain('OPENCODE_SHARED_HOME="/home/dev/.codex-shared/opencode"')
139139
expect(entrypoint).toContain('OPENCODE_CONFIG_DIR="/home/dev/.config/opencode"')
140+
expect(entrypoint).toContain('su - dev -s /bin/bash -c "bash -lc')
141+
expect(entrypoint).toContain('. /etc/profile 2>/dev/null || true;')
142+
expect(entrypoint).toContain("codex exec")
143+
expect(entrypoint).not.toContain("codex --approval-mode full-auto")
140144
expect(entrypoint).toContain('"plugin": ["oh-my-opencode"]')
141145
expect(entrypoint).toContain("branch '$REPO_REF' missing; retrying without --branch")
142146
expect(entrypoint).not.toContain("git ls-remote --symref")

0 commit comments

Comments
 (0)