Skip to content

Commit 908b894

Browse files
committed
fix(docker): restore config parent ownership
1 parent 3a03dd5 commit 908b894

5 files changed

Lines changed: 24 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
1010

1111
- Gateway/perf: tighten restart and startup benchmark failure handling so long profiling runs, failed probes, and fresh Linux runners no longer produce false passing or `n/a` results.
1212
- Checks: keep intentional Knip unused-file findings optional so full CI and sparse proof workspaces stay aligned.
13+
- Docker: restore writable `~/.config` in runtime images. Fixes #85968. Thanks @hkoessler and @Bartok9.
1314
- Tests: normalize bundled plugin lifecycle probe paths and state-root lookup so native Windows release sweeps accept valid packaged plugin installs.
1415
- Config: keep benign legacy metadata write anomalies out of default doctor and config command output while preserving explicit anomaly logging for diagnostics.
1516
- Codex: log when implicit app-server `never` approvals are promoted for OpenClaw tool policy, including whether the trigger was a `before_tool_call` hook or trusted tool policy.

Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,17 @@ RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
287287

288288
# Pre-create default named-volume mount points so first-run Docker volumes copy
289289
# node ownership from the image instead of starting as root-owned directories.
290-
RUN install -d -m 0700 -o node -g node \
290+
# NOTE: /home/node/.config must be created with node ownership first so that
291+
# the leaf /home/node/.config/openclaw inherits the correct parent permissions.
292+
# Without this, install -d leaves /home/node/.config as root:root (issue #85968).
293+
RUN install -d -m 0755 -o node -g node /home/node/.config && \
294+
install -d -m 0700 -o node -g node \
291295
/home/node/.openclaw \
292296
/home/node/.openclaw/workspace \
293297
/home/node/.config/openclaw && \
294298
stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' && \
295299
stat -c '%U:%G %a' /home/node/.openclaw/workspace | grep -qx 'node:node 700' && \
300+
stat -c '%U:%G %a' /home/node/.config | grep -qx 'node:node 755' && \
296301
stat -c '%U:%G %a' /home/node/.config/openclaw | grep -qx 'node:node 700'
297302

298303
ENV NODE_ENV=production

scripts/docker/setup.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ echo "==> Fixing data-directory permissions"
554554
# (.openclaw/) inside the workspace gets chowned, not the user's project files.
555555
run_prestart_gateway --user root --entrypoint sh openclaw-gateway -c \
556556
'find /home/node/.openclaw -xdev -exec chown node:node {} +; \
557+
chown node:node /home/node/.config; \
557558
find /home/node/.config/openclaw -xdev -exec chown node:node {} +; \
558559
[ -d /home/node/.openclaw/workspace/.openclaw ] && chown -R node:node /home/node/.openclaw/workspace/.openclaw || true'
559560

src/docker-setup.e2e.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ describe("scripts/docker/setup.sh", () => {
482482
expect(chownIdx).toBeGreaterThanOrEqual(0);
483483
expect(onboardIdx).toBeGreaterThan(chownIdx);
484484
expect(log).toContain("run --rm --no-deps --user root --entrypoint sh openclaw-gateway -c");
485+
expect(log).toContain("chown node:node /home/node/.config");
485486
});
486487

487488
it("precreates auth profile secret key dir outside the mounted state dir", async () => {

src/dockerfile.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,15 +327,24 @@ describe("Dockerfile", () => {
327327
it("pre-creates named-volume mount points before switching to the node user", async () => {
328328
const dockerfile = await readFile(dockerfilePath, "utf8");
329329
const runtimeStageIndex = dockerfile.lastIndexOf("FROM base-runtime");
330-
const stateDirIndex = dockerfile.indexOf(
331-
"RUN install -d -m 0700 -o node -g node \\",
330+
const parentConfigDirIndex = dockerfile.indexOf(
331+
"RUN install -d -m 0755 -o node -g node /home/node/.config",
332332
runtimeStageIndex,
333333
);
334+
const stateDirIndex = dockerfile.indexOf(
335+
"install -d -m 0700 -o node -g node \\",
336+
parentConfigDirIndex,
337+
);
334338
const userIndex = dockerfile.indexOf("USER node", runtimeStageIndex);
335339

336340
expect(runtimeStageIndex).toBeGreaterThan(-1);
341+
// Regression: /home/node/.config parent must be created with node ownership
342+
// before the leaf .config/openclaw dir (issue #85968).
343+
expect(parentConfigDirIndex).toBeGreaterThan(-1);
337344
expect(stateDirIndex).toBeGreaterThan(-1);
338345
expect(userIndex).toBeGreaterThan(-1);
346+
expect(parentConfigDirIndex).toBeGreaterThan(runtimeStageIndex);
347+
expect(parentConfigDirIndex).toBeLessThan(stateDirIndex);
339348
expect(stateDirIndex).toBeGreaterThan(runtimeStageIndex);
340349
expect(stateDirIndex).toBeLessThan(userIndex);
341350
expect(dockerfile).not.toContain("mkdir -p /home/node/.openclaw");
@@ -347,6 +356,10 @@ describe("Dockerfile", () => {
347356
expect(dockerfile).toContain(
348357
"stat -c '%U:%G %a' /home/node/.openclaw/workspace | grep -qx 'node:node 700'",
349358
);
359+
// Regression: assert parent /home/node/.config is also node-owned (issue #85968).
360+
expect(dockerfile).toContain(
361+
"stat -c '%U:%G %a' /home/node/.config | grep -qx 'node:node 755'",
362+
);
350363
expect(dockerfile).toContain(
351364
"stat -c '%U:%G %a' /home/node/.config/openclaw | grep -qx 'node:node 700'",
352365
);

0 commit comments

Comments
 (0)