openclaw - ✅(Solved) Fix Gateway crash loop in 2026.4.26: `logging.file` paths starting with `~` are no longer expanded [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#73587Fetched 2026-04-29 06:17:50
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Timeline (top)
cross-referenced ×3commented ×2referenced ×1

After updating from 2026.4.24 to 2026.4.26, the gateway enters a tight crash-loop on startup if openclaw.json has logging.file set to a path beginning with ~. Under launchd this produces a respawn-loop every ~1.85s that takes the entire stack down (gateway, channels, sidecars, crons all unreachable).

Error Message

[openclaw] CLI failed: Error: ENOENT: no such file or directory, mkdir '~/.openclaw/logs'
    at Object.mkdirSync (node:fs:1334:26)
    at buildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:433:5)
    at getLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:496:31)
    at getChildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:502:15)
    at emitLog (file:///.../node_modules/openclaw/dist/subsystem-rHhUC6qs.js:163:30)
    at Object.info (file:///.../node_modules/openclaw/dist/subsystem-rHhUC6qs.js:196:4)
    at runGatewayCommand$1 (file:///.../node_modules/openclaw/dist/gateway-cli-l4SoRD2p.js:848:13)

mkdir fails because ~ is a shell construct, not a filesystem one. fs.mkdirSync does not expand it.

Root Cause

In dist/logger-BYIbL3gn.js, resolveActiveLogFile returns the configured path verbatim — no tilde or $HOME expansion — and buildLogger then calls fs.mkdirSync(path.dirname(activeFile)) on the literal string:

function resolveActiveLogFile(file) {
    if (!isRollingPath(file)) return file;        // ← returns "~/.openclaw/logs/gateway.log" as-is
    return rollingPathForDate(path.dirname(file), new Date());
}

function buildLogger(settings) {
    ...
    let activeFile = resolveActiveLogFile(settings.file);
    fs.mkdirSync(path.dirname(activeFile), { recursive: true });   // ← line 433: ENOENT on '~/...'
    ...
}

The same config worked under 2026.4.24, so this is a regression — either a previously-existing tilde-expansion call was removed, or some earlier call site that expanded the path was bypassed.

Fix Action

Workaround

Replace ~ with the absolute path in openclaw.json:

-    "file": "~/.openclaw/logs/gateway.log",
+    "file": "/Users/<user>/.openclaw/logs/gateway.log",

PR fix notes

PR #73614: fix(logging): expand leading tilde in logging.file (#73587)

Description (problem / solution / changelog)

What

Fixes #73587. After updating from 2026.4.24 to 2026.4.26, gateways with openclaw.json logging.file set to a ~/-prefixed path crash on startup with ENOENT: no such file or directory, mkdir '~/...' and (under launchd / systemd) enter a tight respawn loop that takes the entire stack down — gateway, channels, sidecars, crons.

Root cause

logging.file paths come straight from openclaw.json. ~ is a shell construct; Node's fs.mkdirSync does not expand it. resolveActiveLogFile in src/logging/logger.ts was returning the literal string, and buildLogger then called fs.mkdirSync(path.dirname(activeFile), { recursive: true }) on '~/.openclaw/logs' directly:

[openclaw] CLI failed: Error: ENOENT: no such file or directory, mkdir '~/.openclaw/logs'
    at Object.mkdirSync (node:fs:1334:26)
    at buildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:433:5)

The same config worked under 2026.4.24, so this is a regression where an earlier expansion site was bypassed.

Fix

Reuse the existing expandHomePrefix helper from src/infra/home-dir.ts inside resolveActiveLogFile. The helper is already used by state-migrations.ts:1125 and exec-allowlist-pattern.ts:74, follows the same OPENCLAW_HOME / HOME / USERPROFILE / os.homedir() resolution order as the rest of the codebase, and short-circuits cleanly when the input does not start with ~. With the expansion happening up front, both the isRollingPath matcher (which inspects the basename, unchanged) and the downstream fs.mkdirSync see an absolute path.

function resolveActiveLogFile(file: string): string {
  const expanded = expandHomePrefix(file);
  if (!isRollingPath(expanded)) {
    return expanded;
  }
  return rollingPathForDate(path.dirname(expanded), new Date());
}

Pre-implement audit

  1. Existing-helper check (vincentkoc #57341 lesson). grep finds expandHomePrefix already exported from src/infra/home-dir.ts and used at two production call sites. Reuse rather than introduce a new helper. ✅
  2. Shared-helper caller check (steipete #60623 lesson). resolveActiveLogFile is module-private and called only from buildLogger (line 521 + line 532). No silent contract change for other callers. ✅
  3. Broader-fix rival scan (steipete #68270 lesson). Zero rival PRs reference #73587. ✅

Test changes

Three new regression tests in src/logging/logger.settings.test.ts, exposed via the existing __test__ export:

  • expands a leading tilde to the home directory — pins the ~/.openclaw/logs/gateway.log<home>/.openclaw/logs/gateway.log shape.
  • leaves absolute paths unchanged — guards against an over-eager helper.
  • expands tildes inside rolling-path inputs and keeps the rolling pattern — confirms the rolling-pattern detection still fires when the input is ~/.openclaw/logs/openclaw-YYYY-MM-DD.log.

The tests resolve the effective home with the same precedence the production helper uses (OPENCLAW_HOMEHOMEUSERPROFILEos.homedir()), so they remain stable under the per-suite home-dir overrides the harness sets up.

Verified locally

npx oxlint src/logging/logger.ts src/logging/logger.settings.test.ts
# Found 0 warnings and 0 errors.

npx vitest run src/logging/logger.settings.test.ts
# Tests  6 passed (6)

npx vitest run src/logging/redact.test.ts src/logging/logger-redaction-behavior.test.ts
# Tests  36 passed (36)

(Two pre-existing src/logging/-suite-level failures in logger-redaction-behavior.test.ts and redact.test.ts reproduce on plain main without this patch and are independent of this change — confirmed via stash + re-run on the f256eeba43 baseline.)

lobster-biscuit: 73587-logging-file-tilde-expand

Sign-Off:

  • I have read and agree to the OpenClaw Contributor License Agreement.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/logging/logger.settings.test.ts (modified, +47/-0)
  • src/logging/logger.ts (modified, +12/-4)

Code Example

"logging": {
     "level": "info",
     "file": "~/.openclaw/logs/gateway.log",
     "consoleLevel": "warn",
     "redactSensitive": "tools"
   }

---

[openclaw] CLI failed: Error: ENOENT: no such file or directory, mkdir '~/.openclaw/logs'
    at Object.mkdirSync (node:fs:1334:26)
    at buildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:433:5)
    at getLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:496:31)
    at getChildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:502:15)
    at emitLog (file:///.../node_modules/openclaw/dist/subsystem-rHhUC6qs.js:163:30)
    at Object.info (file:///.../node_modules/openclaw/dist/subsystem-rHhUC6qs.js:196:4)
    at runGatewayCommand$1 (file:///.../node_modules/openclaw/dist/gateway-cli-l4SoRD2p.js:848:13)

---

function resolveActiveLogFile(file) {
    if (!isRollingPath(file)) return file;        // ← returns "~/.openclaw/logs/gateway.log" as-is
    return rollingPathForDate(path.dirname(file), new Date());
}

function buildLogger(settings) {
    ...
    let activeFile = resolveActiveLogFile(settings.file);
    fs.mkdirSync(path.dirname(activeFile), { recursive: true });   // ← line 433: ENOENT on '~/...'
    ...
}

---

import os from "node:os";

function expandHome(p) {
    if (typeof p !== "string") return p;
    if (p === "~") return os.homedir();
    if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
    return p;
}

function resolveActiveLogFile(file) {
    const expanded = expandHome(file);
    if (!isRollingPath(expanded)) return expanded;
    return rollingPathForDate(path.dirname(expanded), new Date());
}

---

-    "file": "~/.openclaw/logs/gateway.log",
+    "file": "/Users/<user>/.openclaw/logs/gateway.log",
RAW_BUFFERClick to expand / collapse

Summary

After updating from 2026.4.24 to 2026.4.26, the gateway enters a tight crash-loop on startup if openclaw.json has logging.file set to a path beginning with ~. Under launchd this produces a respawn-loop every ~1.85s that takes the entire stack down (gateway, channels, sidecars, crons all unreachable).

Environment

  • macOS 26.3 (arm64), Node 25.8.1
  • Install path: /Users/agent/.local/lib/node_modules/openclaw/
  • Service: LaunchAgent ai.openclaw.gateway
  • Pre-update version: 2026.4.24 (working with this exact config)
  • Post-update version: 2026.4.26 (crashes immediately)

Reproduction

  1. Have openclaw.json with:
    "logging": {
      "level": "info",
      "file": "~/.openclaw/logs/gateway.log",
      "consoleLevel": "warn",
      "redactSensitive": "tools"
    }
  2. Start the gateway with 2026.4.26.
  3. Observe the gateway exit immediately with the error below, and (under launchd) be respawned in a tight loop.

Error

[openclaw] CLI failed: Error: ENOENT: no such file or directory, mkdir '~/.openclaw/logs'
    at Object.mkdirSync (node:fs:1334:26)
    at buildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:433:5)
    at getLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:496:31)
    at getChildLogger (file:///.../node_modules/openclaw/dist/logger-BYIbL3gn.js:502:15)
    at emitLog (file:///.../node_modules/openclaw/dist/subsystem-rHhUC6qs.js:163:30)
    at Object.info (file:///.../node_modules/openclaw/dist/subsystem-rHhUC6qs.js:196:4)
    at runGatewayCommand$1 (file:///.../node_modules/openclaw/dist/gateway-cli-l4SoRD2p.js:848:13)

mkdir fails because ~ is a shell construct, not a filesystem one. fs.mkdirSync does not expand it.

Root cause

In dist/logger-BYIbL3gn.js, resolveActiveLogFile returns the configured path verbatim — no tilde or $HOME expansion — and buildLogger then calls fs.mkdirSync(path.dirname(activeFile)) on the literal string:

function resolveActiveLogFile(file) {
    if (!isRollingPath(file)) return file;        // ← returns "~/.openclaw/logs/gateway.log" as-is
    return rollingPathForDate(path.dirname(file), new Date());
}

function buildLogger(settings) {
    ...
    let activeFile = resolveActiveLogFile(settings.file);
    fs.mkdirSync(path.dirname(activeFile), { recursive: true });   // ← line 433: ENOENT on '~/...'
    ...
}

The same config worked under 2026.4.24, so this is a regression — either a previously-existing tilde-expansion call was removed, or some earlier call site that expanded the path was bypassed.

Suggested fix

Expand a leading ~ (or ~/) to os.homedir() inside resolveActiveLogFile (or in a new expandHome helper used wherever a configured path crosses into fs.*):

import os from "node:os";

function expandHome(p) {
    if (typeof p !== "string") return p;
    if (p === "~") return os.homedir();
    if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
    return p;
}

function resolveActiveLogFile(file) {
    const expanded = expandHome(file);
    if (!isRollingPath(expanded)) return expanded;
    return rollingPathForDate(path.dirname(expanded), new Date());
}

Worth applying this to any other config-derived path that hits fs — e.g. agents.workspace, plugin paths — since users with ~/-prefixed paths in those fields will hit the same regression the moment a code path tries to mkdir them.

Workaround

Replace ~ with the absolute path in openclaw.json:

-    "file": "~/.openclaw/logs/gateway.log",
+    "file": "/Users/<user>/.openclaw/logs/gateway.log",

Severity

High. Anyone whose openclaw.json was written with a ~/-prefixed logging.file (which is intuitive and common) will have their entire gateway taken down on update. Under launchd / systemd the respawn-loop also produces a lot of error-log churn (~30 crash loops per minute) until manually intervened.

extent analysis

TL;DR

The issue can be fixed by expanding the leading ~ in the logging.file path to the user's home directory using the os.homedir() function.

Guidance

  • Identify all config-derived paths that may contain a leading ~ and apply the expandHome function to them.
  • Update the resolveActiveLogFile function to use the expandHome function to expand the ~ in the logging.file path.
  • As a temporary workaround, replace the ~ with the absolute path in the openclaw.json file.
  • Review other parts of the code that use fs functions to ensure they handle paths with ~ correctly.

Example

function expandHome(p) {
    if (typeof p !== "string") return p;
    if (p === "~") return os.homedir();
    if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
    return p;
}

function resolveActiveLogFile(file) {
    const expanded = expandHome(file);
    if (!isRollingPath(expanded)) return expanded;
    return rollingPathForDate(path.dirname(expanded), new Date());
}

Notes

This fix assumes that the issue is caused by the removal of a previously existing tilde-expansion call or a change in the code path that bypassed the expansion. The expandHome function should be applied to all config-derived paths that may contain a leading ~ to prevent similar issues.

Recommendation

Apply the suggested fix by updating the resolveActiveLogFile function to use the expandHome function to expand the ~ in the logging.file path. This will ensure that the logging.file path is correctly expanded to the user's home directory.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Gateway crash loop in 2026.4.26: `logging.file` paths starting with `~` are no longer expanded [1 pull requests, 2 comments, 2 participants]