Skip to content

Commit 521b1af

Browse files
committed
fix(app): pin controller compose project name
1 parent a9ca6cc commit 521b1af

5 files changed

Lines changed: 88 additions & 17 deletions

File tree

packages/app/src/docker-git/controller-compose.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ export type ControllerComposeFiles = {
2323
readonly runtimeOverlayPath: string | null
2424
}
2525

26+
export const controllerComposeProjectName = "docker-git"
27+
28+
// CHANGE: pin the controller compose project name across checkout directories
29+
// WHY: fixed controller container_name must be recreated by the same compose project, not by cwd-derived names
30+
// QUOTE(ТЗ): "container name \"/docker-git-api\" is already in use"
31+
// REF: user-message-2026-06-06-controller-compose-conflict
32+
// SOURCE: n/a
33+
// FORMAT THEOREM: forall cwd: compose_project(controller_bootstrap(cwd)) = "docker-git"
34+
// PURITY: CORE
35+
// EFFECT: n/a
36+
// INVARIANT: controller bootstrap compose commands use one global project name
37+
// COMPLEXITY: O(1)
38+
export const controllerComposeProjectArgs: ReadonlyArray<string> = [
39+
"--project-name",
40+
controllerComposeProjectName
41+
]
42+
2643
const skillerSubmodulePath = "third_party/skiller-desktop-skills-manager"
2744
const skillerPackagePath = `${skillerSubmodulePath}/package.json`
2845

packages/app/src/docker-git/controller-docker.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ import type * as FileSystem from "@effect/platform/FileSystem"
44
import type * as Path from "@effect/platform/Path"
55
import { Effect } from "effect"
66

7-
import { composeFilesToArgs, prepareControllerRevision, resolveControllerComposeFiles } from "./controller-compose.js"
8-
import { readCurrentContainerName } from "./controller-hostname.js"
9-
import {
10-
runCommandCaptureWithFailureOutput,
11-
runCommandExitCode,
12-
runCommandExitCodeStreaming,
13-
runCommandWithCapturedOutput
14-
} from "./frontend-lib/shell/command-runner.js"
15-
7+
import * as ControllerCompose from "./controller-compose.js"
168
import { type DockerProbeOutcome, renderDockerAccessDeniedMessage } from "./controller-docker-diagnostics.js"
9+
import { readCurrentContainerName } from "./controller-hostname.js"
1710
import {
1811
type DockerNetworkIps,
1912
parseDockerNetworkIps,
2013
resolveConfiguredApiBaseUrl,
2114
uniqueStrings
2215
} from "./controller-reachability.js"
2316
import { parseControllerRevisionEnvOutput } from "./controller-revision.js"
17+
import {
18+
runCommandCaptureWithFailureOutput,
19+
runCommandExitCode,
20+
runCommandExitCodeStreaming,
21+
runCommandWithCapturedOutput
22+
} from "./frontend-lib/shell/command-runner.js"
2423
import type { ControllerBootstrapError } from "./host-errors.js"
2524

2625
export {
@@ -32,10 +31,7 @@ export {
3231
} from "./controller-compose.js"
3332
export { parseControllerDockerRuntime } from "./controller-runtime.js"
3433

35-
export type ControllerRuntime =
36-
| CommandExecutor.CommandExecutor
37-
| FileSystem.FileSystem
38-
| Path.Path
34+
export type ControllerRuntime = CommandExecutor.CommandExecutor | FileSystem.FileSystem | Path.Path
3935

4036
export const controllerContainerName = process.env["DOCKER_GIT_API_CONTAINER_NAME"]?.trim() || "docker-git-api"
4137

@@ -383,10 +379,10 @@ export const runCompose = (
383379
): Effect.Effect<void, ControllerBootstrapError, ControllerRuntime> =>
384380
Effect.gen(function*(_) {
385381
const dockerCommand = yield* _(resolveDockerCommand())
386-
const composeFiles = yield* _(resolveControllerComposeFiles())
387382
const invocation = buildDockerInvocation(dockerCommand, [
388383
"compose",
389-
...composeFilesToArgs(composeFiles),
384+
...ControllerCompose.controllerComposeProjectArgs,
385+
...ControllerCompose.composeFilesToArgs(yield* _(ControllerCompose.resolveControllerComposeFiles())),
390386
...args
391387
])
392388
const exitCode = yield* _(
@@ -440,7 +436,7 @@ export const inspectControllerRevision = (): Effect.Effect<
440436
)
441437

442438
export const prepareLocalControllerRevision = (): Effect.Effect<string, ControllerBootstrapError, ControllerRuntime> =>
443-
prepareControllerRevision()
439+
ControllerCompose.prepareControllerRevision()
444440

445441
export const inspectContainerNetworks = (
446442
containerName: string

packages/app/src/docker-git/controller-image-revision.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Effect } from "effect"
22

3-
import { composeFilesToArgs, resolveControllerComposeFiles } from "./controller-compose.js"
3+
import {
4+
composeFilesToArgs,
5+
controllerComposeProjectArgs,
6+
resolveControllerComposeFiles
7+
} from "./controller-compose.js"
48
import { type ControllerRuntime, runDockerCapture, runDockerCaptureWithFailureOutput } from "./controller-docker.js"
59
import { parseControllerRevisionLabelOutput } from "./controller-revision.js"
610
import type { ControllerBootstrapError } from "./host-errors.js"
@@ -131,6 +135,7 @@ const inspectControllerComposeImageName = (): Effect.Effect<
131135
runDockerCapture(
132136
[
133137
"compose",
138+
...controllerComposeProjectArgs,
134139
...composeFilesToArgs(composeFiles),
135140
"config",
136141
"--images"

packages/app/tests/docker-git/controller-compose.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import * as fc from "fast-check"
88
import { resolveControllerRuntimeOverlayPath } from "../../src/docker-git/controller-compose-runtime.js"
99
import {
1010
controllerBuildSkillerEnvKey,
11+
controllerComposeProjectName,
1112
controllerGpuModeEnvKey,
1213
ensureSkillerSubmoduleInitialized,
1314
prepareControllerRevision,
1415
resolveControllerComposeFiles
1516
} from "../../src/docker-git/controller-compose.js"
17+
import { runCompose } from "../../src/docker-git/controller-docker.js"
1618
import { controllerRevisionEnvKey } from "../../src/docker-git/controller-revision.js"
1719
import { controllerDockerRuntimeEnvKey } from "../../src/docker-git/controller-runtime.js"
1820
import type { TestCommandResult } from "./fixtures/command-executor.js"
@@ -205,6 +207,33 @@ const assertControllerComposeProperty = <PropertyArgs>(property: fc.IAsyncProper
205207
})
206208

207209
describe("controller compose preparation", () => {
210+
it.effect("runs controller compose under the stable controller project name", () => {
211+
const startedCommands: Array<string> = []
212+
213+
return withMinimalControllerRoot(() =>
214+
Effect.gen(function*(_) {
215+
yield* _(
216+
withControllerEnv([
217+
[controllerBuildSkillerEnvKey, "0"],
218+
[controllerDockerRuntimeEnvKey, undefined],
219+
[controllerGpuModeEnvKey, undefined]
220+
])
221+
)
222+
yield* _(
223+
runCompose(["up", "-d"]).pipe(
224+
Effect.provide(recordedCommandExecutorLayer(startedCommands, emptyCommandResult))
225+
)
226+
)
227+
228+
const composeCommand = startedCommands.find((command) =>
229+
command.startsWith(`docker compose --project-name ${controllerComposeProjectName} -f `)
230+
)
231+
expect(composeCommand).toBeDefined()
232+
expect(composeCommand?.endsWith(" up -d")).toBe(true)
233+
})
234+
).pipe(Effect.provide(NodeContext.layer))
235+
})
236+
208237
it.effect("does not initialize the Skiller submodule when package metadata already exists", () => {
209238
const startedCommands: Array<string> = []
210239

packages/app/tests/docker-git/controller-image-revision.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { describe, expect, it } from "@effect/vitest"
44
import { Effect, Either } from "effect"
55
import * as fc from "fast-check"
66

7+
import { controllerComposeProjectArgs } from "../../src/docker-git/controller-compose.js"
78
import { inspectControllerImageRevision } from "../../src/docker-git/controller-image-revision.js"
89
import type { ControllerBootstrapError } from "../../src/docker-git/host-errors.js"
910
import {
@@ -189,6 +190,29 @@ const expectRevisionFailureMessage = (
189190
)
190191

191192
describe("controller image revision", () => {
193+
it.effect("resolves compose images under the stable controller project name", () => {
194+
const composeImageCommands: Array<ReadonlyArray<string>> = []
195+
196+
return inspectRevisionWithCommandHandler((command) => {
197+
if (command.command === "docker" && command.args.includes("--images")) {
198+
composeImageCommands.push(command.args)
199+
return { exitCode: 0, stderr: "", stdout: "app-api:latest\n" }
200+
}
201+
if (command.command === "docker" && command.args.includes("image") && command.args.includes("inspect")) {
202+
return { exitCode: 0, stderr: "", stdout: " rev123 \n" }
203+
}
204+
return emptyCommandResult
205+
}).pipe(
206+
Effect.map((revision) => {
207+
expect(revision).toBe("rev123")
208+
expect(composeImageCommands[0]?.slice(0, 1 + controllerComposeProjectArgs.length)).toEqual([
209+
"compose",
210+
...controllerComposeProjectArgs
211+
])
212+
})
213+
)
214+
})
215+
192216
it.effect("falls back to null for non-reusable compose image output cardinalities", () =>
193217
Effect.tryPromise({
194218
catch: (cause) => cause,

0 commit comments

Comments
 (0)