openclaw - ✅(Solved) Fix fix(plugins): legacy runtime-deps lock without starttime/createdAtMs not expired [2 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#74948Fetched 2026-05-01 05:39:44
View on GitHub
Comments
2
Participants
2
Timeline
5
Reactions
2
Timeline (top)
cross-referenced ×3commented ×2

After fixing #74346 (lock staleness check uses PID alone), there remains an edge case for legacy lock files created before the pidStartTimeMs and createdAtMs fields were added.

When a lock file has no starttime AND no createdAtMs, shouldRemoveRuntimeDepsLock() falls through to return false — the lock is considered fresh forever because pid is alive.

Root Cause

When a lock file has no starttime AND no createdAtMs, shouldRemoveRuntimeDepsLock() falls through to return false — the lock is considered fresh forever because pid is alive.

Fix Action

Fix

In shouldRemoveRuntimeDepsLock(), after checking starttime, add fallback staleness checks:

if (typeof owner.createdAtMs === "number" && owner.createdAtMs > 0) {
  return nowMs - owner.createdAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}
const legacyObservedAtMs = latestFiniteMs([
  owner.lockDirMtimeMs,
  owner.ownerFileMtimeMs,
]);
if (typeof legacyObservedAtMs === "number") {
  return nowMs - legacyObservedAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}

PR fix notes

PR #74950: fix(plugins): expire legacy runtime-deps locks in Docker when lock dir is stale

Description (problem / solution / changelog)

Summary

After fixing #74346 (lock staleness check uses PID alone), there remains an edge case for legacy lock files created before the pidStartTimeMs and createdAtMs fields were added.

When a lock file has no starttime AND no createdAtMs, shouldRemoveRuntimeDepsLock() falls through to return false — the lock is considered fresh forever because pid is alive.

Reproduction

  1. Run OpenClaw in Docker with version before fix 2d885a2402 (2026.4.27 or earlier)
  2. Force-kill container (during upgrade docker kill, unhandled exception, etc.) — leaves stale .openclaw-runtime-deps.lock/
  3. Start new container (PID 1, same namespace)
  4. New process reads legacy lock: pid=1 alive=true, no starttime, no createdAtMs
  5. shouldRemoveRuntimeDepsLock() returns false → 5-min timeout → crash loop

Evidence

Lock file from ~22 hours ago (Wednesday, April 29, 2026 at 5:33:24.809 PM [GMT+07:00]):

$ cat .openclaw-runtime-deps.lock/owner.json 
{
  "pid": 1,
  "createdAtMs": 1777458804809
}

Fix

In shouldRemoveRuntimeDepsLock(), after checking starttime, add fallback staleness checks

Related

  • #74346 — original PID-alive bug (fixed in 2d885a2402)
  • #73520 — stale plugin-runtime-deps crash-loop
  • #73846 — Docker/Unraid gateway never healthy

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/plugins/bundled-runtime-deps-lock.ts (modified, +7/-0)
  • src/plugins/bundled-runtime-deps.test.ts (modified, +30/-0)

PR #75183: fix: simplify bundled runtime dependency repair

Description (problem / solution / changelog)

Summary

This PR unifies bundled plugin runtime-dependency repair around the package-level plan and lets npm/pnpm own dependency convergence once OpenClaw has decided a repair is required.

What changed:

  • Build the active bundled plugin dependency plan before startup/runtime imports, then repair the selected package-level install root once instead of per-plugin ad hoc scans.
  • Treat an existing node_modules tree without complete generated materialization as incomplete, even when package sentinels such as node_modules/<dep>/package.json exist.
  • Force the package-manager repair once the planner has marked a tree incomplete, so writing the generated install manifest cannot accidentally turn the repair into a no-op.
  • Keep post-install verification on requested packages and declared entry files, so package-manager success is not trusted until the staged tree is actually usable.
  • Accept generated manifest supersets for narrower plugin loads so a complete package-level stage is reused rather than pruned/reinstalled.
  • Preserve config/doctor/hot-reload behavior: config determines the plugin plan; doctor/config edits enter plan mode; startup only repairs when the plan or materialization requires it.

Fixes / related reports

Fixes #75309.

Supersedes the implementation approach in #75310 by keeping the same narrow idea but closing the reviewed hole where a generated manifest plus a no-main package sentinel could still look materialized.

Also hardens the recovery side of the already-addressed reports #75296 and #75304:

  • #75296: post-install verification already fails closed when npm/pnpm reports success but requested packages are missing; this PR additionally makes later doctor/startup repair recover from the leftover partial tree.
  • #75304: mirrored root dependencies and manifest-superset reuse already address the json5/prune crash loop; this PR prevents interrupted runtime-deps stages from staying stuck behind false package sentinels.

Not claimed: #73520, #74948, #74963, #75071, and #75288 are nearby runtime-deps lifecycle issues with different root causes.

Verification

Local:

  • pnpm docs:list
  • pnpm exec oxfmt --check --threads=1 src/plugins/bundled-runtime-deps-install.ts src/plugins/bundled-runtime-deps.ts src/plugins/bundled-runtime-deps-materialization.ts src/plugins/bundled-runtime-deps.test.ts src/commands/doctor-bundled-plugin-runtime-deps.test.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md
  • pnpm check:changelog-attributions
  • git diff --check
  • pnpm test src/plugins/bundled-runtime-deps.test.ts src/commands/doctor-bundled-plugin-runtime-deps.test.ts src/gateway/server-startup-plugins.test.ts src/gateway/server.reload.test.ts -- --reporter=verbose

Blacksmith/Testbox:

  • tbx_01kqgsehj3tf33k2dmzy35ds7j: pnpm test src/plugins/bundled-runtime-deps.test.ts src/commands/doctor-bundled-plugin-runtime-deps.test.ts src/gateway/server-startup-plugins.test.ts src/gateway/server.reload.test.ts -- --reporter=verbose passed, 3 Vitest shards / 177 tests.

Broad pnpm check:changed was attempted on tbx_01kqgrw1phqcm20c9w06n1w4wx, but the Testbox full-sync omitted the tracked-but-gitignored pnpm-lock.yaml; the workaround polluted remote node_modules into the changed-file scan and tsgolint was later SIGKILLed. I do not count that polluted run as product signal.

Changed files

  • CHANGELOG.md (modified, +4/-0)
  • docs/cli/channels.md (modified, +3/-0)
  • docs/cli/configure.md (modified, +1/-0)
  • docs/cli/gateway.md (modified, +1/-1)
  • docs/cli/onboard.md (modified, +2/-0)
  • docs/cli/plugins.md (modified, +6/-2)
  • docs/docs.json (modified, +1/-0)
  • docs/gateway/doctor.md (modified, +1/-1)
  • docs/plugins/dependency-resolution.md (added, +214/-0)
  • docs/tools/acp-agents.md (modified, +2/-1)
  • docs/tools/plugin.md (modified, +6/-3)
  • scripts/lib/bundled-runtime-deps-install.mjs (modified, +18/-3)
  • scripts/postinstall-bundled-plugins.mjs (modified, +4/-146)
  • scripts/release-check.ts (modified, +1/-1)
  • src/channels/plugins/bundled.shape-guard.test.ts (modified, +2/-2)
  • src/channels/plugins/read-only.test.ts (modified, +14/-14)
  • src/channels/plugins/read-only.ts (modified, +3/-3)
  • src/cli/command-bootstrap.test.ts (modified, +28/-3)
  • src/cli/command-bootstrap.ts (modified, +12/-5)
  • src/cli/command-catalog.ts (modified, +28/-4)
  • src/cli/command-execution-startup.test.ts (modified, +7/-0)
  • src/cli/command-execution-startup.ts (modified, +1/-0)
  • src/cli/command-path-policy.test.ts (modified, +59/-68)
  • src/cli/command-path-policy.ts (modified, +1/-0)
  • src/cli/command-startup-policy.test.ts (modified, +2/-0)
  • src/cli/command-startup-policy.ts (modified, +23/-7)
  • src/cli/plugin-registry-loader.test.ts (modified, +11/-7)
  • src/cli/plugin-registry-loader.ts (modified, +10/-7)
  • src/cli/plugins-cli.list.test.ts (modified, +41/-3)
  • src/cli/plugins-cli.ts (modified, +8/-456)
  • src/cli/plugins-command-helpers.ts (modified, +8/-0)
  • src/cli/plugins-deps-command.test.ts (modified, +47/-32)
  • src/cli/plugins-deps-command.ts (modified, +40/-42)
  • src/cli/plugins-inspect-command.ts (added, +361/-0)
  • src/cli/plugins-list-command.ts (added, +114/-0)
  • src/cli/program/preaction.test.ts (modified, +9/-3)
  • src/commands/agents.providers.test.ts (modified, +1/-1)
  • src/commands/agents.providers.ts (modified, +2/-2)
  • src/commands/channels.list.auth-profiles.test.ts (modified, +2/-2)
  • src/commands/channels.remove.test.ts (modified, +45/-20)
  • src/commands/channels.resolve.test.ts (modified, +30/-24)
  • src/commands/channels/capabilities.ts (modified, +1/-1)
  • src/commands/channels/list.ts (modified, +1/-1)
  • src/commands/channels/remove.ts (modified, +9/-2)
  • src/commands/channels/resolve.ts (modified, +6/-1)
  • src/commands/channels/status-config-format.ts (modified, +1/-1)
  • src/commands/configure.wizard.test.ts (modified, +31/-1)
  • src/commands/configure.wizard.ts (modified, +2/-0)
  • src/commands/doctor-bundled-plugin-runtime-deps.test.ts (modified, +50/-18)
  • src/commands/doctor-bundled-plugin-runtime-deps.ts (modified, +25/-24)
  • src/commands/doctor-security.test.ts (modified, +2/-2)
  • src/commands/doctor-security.ts (modified, +1/-1)
  • src/commands/doctor/shared/channel-doctor.test.ts (modified, +1/-1)
  • src/commands/doctor/shared/channel-doctor.ts (modified, +1/-1)
  • src/commands/health.snapshot.test.ts (modified, +2/-2)
  • src/commands/health.ts (modified, +2/-2)
  • src/commands/onboard-non-interactive.gateway.test.ts (modified, +15/-0)
  • src/commands/onboard-non-interactive/local.ts (modified, +2/-0)
  • src/commands/post-config-runtime-deps.test.ts (added, +164/-0)
  • src/commands/post-config-runtime-deps.ts (added, +133/-0)
  • src/commands/status-all/channels.ts (modified, +3/-3)
  • src/commands/status-runtime-shared.test.ts (modified, +1/-1)
  • src/commands/status-runtime-shared.ts (modified, +1/-1)
  • src/commands/status.link-channel.ts (modified, +1/-1)
  • src/commands/status.scan-overview.test.ts (modified, +2/-2)
  • src/commands/status.scan-overview.ts (modified, +1/-1)
  • src/commands/status.scan.test.ts (modified, +3/-3)
  • src/gateway/config-reload-plan.ts (modified, +29/-0)
  • src/gateway/config-reload.test.ts (modified, +6/-0)
  • src/gateway/config-reload.ts (modified, +1/-0)
  • src/gateway/server-aux-handlers.test.ts (modified, +1/-0)
  • src/gateway/server-plugin-bootstrap.ts (modified, +5/-1)
  • src/gateway/server-plugins.ts (modified, +5/-1)
  • src/gateway/server-reload-handlers.ts (modified, +52/-0)
  • src/gateway/server-runtime-state.test.ts (added, +66/-0)
  • src/gateway/server-runtime-state.ts (modified, +5/-2)
  • src/gateway/server-startup-plugins.test.ts (modified, +130/-55)
  • src/gateway/server-startup-plugins.ts (modified, +145/-89)
  • src/gateway/server-startup-post-attach.test.ts (modified, +56/-0)
  • src/gateway/server-startup-post-attach.ts (modified, +26/-6)
  • src/gateway/server.impl.ts (modified, +66/-15)
  • src/gateway/server.reload.test.ts (modified, +92/-0)
  • src/gateway/server/readiness.test.ts (modified, +18/-0)
  • src/gateway/server/readiness.ts (modified, +3/-1)
  • src/infra/channel-summary.ts (modified, +1/-1)
  • src/infra/npm-install-env.ts (modified, +4/-0)
  • src/infra/safe-package-install.test.ts (modified, +10/-0)
  • src/infra/safe-package-install.ts (modified, +5/-0)
  • src/plugin-sdk/facade-loader.test.ts (modified, +1/-1)
  • src/plugins/bundled-runtime-deps-activity.ts (modified, +1/-5)
  • src/plugins/bundled-runtime-deps-install.ts (modified, +9/-47)
  • src/plugins/bundled-runtime-deps-lock.ts (modified, +7/-0)
  • src/plugins/bundled-runtime-deps-materialization.ts (modified, +76/-31)
  • src/plugins/bundled-runtime-deps-package-manager.ts (modified, +7/-1)
  • src/plugins/bundled-runtime-deps-roots.ts (modified, +66/-47)
  • src/plugins/bundled-runtime-deps-selection.ts (modified, +5/-5)
  • src/plugins/bundled-runtime-deps.test.ts (modified, +683/-69)
  • src/plugins/bundled-runtime-deps.ts (modified, +249/-80)
  • src/plugins/bundled-runtime-root.test.ts (modified, +109/-3)
  • src/plugins/bundled-runtime-root.ts (modified, +141/-24)

Code Example

if (typeof owner.createdAtMs === "number" && owner.createdAtMs > 0) {
  return nowMs - owner.createdAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}
const legacyObservedAtMs = latestFiniteMs([
  owner.lockDirMtimeMs,
  owner.ownerFileMtimeMs,
]);
if (typeof legacyObservedAtMs === "number") {
  return nowMs - legacyObservedAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}
RAW_BUFFERClick to expand / collapse

Summary

After fixing #74346 (lock staleness check uses PID alone), there remains an edge case for legacy lock files created before the pidStartTimeMs and createdAtMs fields were added.

When a lock file has no starttime AND no createdAtMs, shouldRemoveRuntimeDepsLock() falls through to return false — the lock is considered fresh forever because pid is alive.

Reproduction

  1. Run OpenClaw in Docker with version before fix 2d885a2402 (2026.4.27 or earlier)
  2. Force-kill container (OOM, docker kill, etc.) — leaves stale .openclaw-runtime-deps.lock/
  3. Start new container (PID 1, same namespace)
  4. New process reads legacy lock: pid=1 alive=true, no starttime, no createdAtMs
  5. shouldRemoveRuntimeDepsLock() returns false → 5-min timeout → crash loop

Evidence

Lock file from ~872 days ago:

  • ownerFile=ok (owner.json is valid)
  • pid=1 alive=true (current process)
  • ownerAge=72945443ms (legacy lock, no createdAtMs)
  • lockAge=72945443ms (lock dir untouched)

Fix

In shouldRemoveRuntimeDepsLock(), after checking starttime, add fallback staleness checks:

if (typeof owner.createdAtMs === "number" && owner.createdAtMs > 0) {
  return nowMs - owner.createdAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}
const legacyObservedAtMs = latestFiniteMs([
  owner.lockDirMtimeMs,
  owner.ownerFileMtimeMs,
]);
if (typeof legacyObservedAtMs === "number") {
  return nowMs - legacyObservedAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}

Fix committed

  • Commit: cfa8ae8d6cfix(plugins): expire legacy runtime-deps locks in Docker when lock dir is stale
  • Files: src/plugins/bundled-runtime-deps-lock.ts, `src/plugins/bundled-runtime-deps.test.ts

extent analysis

TL;DR

To fix the issue with legacy lock files, update the shouldRemoveRuntimeDepsLock() function to include fallback staleness checks for locks without starttime and createdAtMs fields.

Guidance

  • Review the shouldRemoveRuntimeDepsLock() function to ensure it correctly handles legacy lock files by checking for the presence of starttime and createdAtMs fields.
  • If these fields are missing, use the lockDirMtimeMs and ownerFileMtimeMs as fallbacks to determine staleness.
  • Verify that the updated function correctly expires legacy runtime-deps locks in Docker when the lock dir is stale.
  • Test the fix with legacy lock files created before the pidStartTimeMs and createdAtMs fields were added.

Example

if (typeof owner.createdAtMs === "number" && owner.createdAtMs > 0) {
  return nowMs - owner.createdAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}
const legacyObservedAtMs = latestFiniteMs([
  owner.lockDirMtimeMs,
  owner.ownerFileMtimeMs,
]);
if (typeof legacyObservedAtMs === "number") {
  return nowMs - legacyObservedAtMs > BUNDLED_RUNTIME_DEPS_LOCK_STALE_MS;
}

Notes

This fix assumes that the lockDirMtimeMs and ownerFileMtimeMs fields are reliable indicators of the lock's staleness. If this is not the case, additional modifications may be necessary.

Recommendation

Apply the workaround by updating the shouldRemoveRuntimeDepsLock() function to include the fallback staleness checks, as this will ensure that legacy runtime-deps locks are correctly expired in Docker.

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 fix(plugins): legacy runtime-deps lock without starttime/createdAtMs not expired [2 pull requests, 2 comments, 2 participants]