openclaw - ✅(Solved) Fix [Bug]: api.resolvePath returns undefined for extension plugins during service start [1 pull requests, 3 comments, 3 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#74676Fetched 2026-04-30 06:21:26
View on GitHub
Comments
3
Participants
3
Timeline
6
Reactions
3
Timeline (top)
commented ×3closed ×1cross-referenced ×1referenced ×1

api.resolvePath('.') returns undefined when called from within a plugin's registered service start() function for extension-based (non-stock) plugins installed under ~/.openclaw/extensions/. This causes any extension plugin that uses api.resolvePath for file I/O during service startup to fail.

Error Message

[clawtalk] ClawTalk service start failed: The "path" argument must be of type string. Received undefined

The error originates from path.dirname(undefined) inside the plugin, where undefined was the result of api.resolvePath('.').

Root Cause

In loader-CPsG_3Jg.js, createApi() wires resolvePath as:

resolvePath: (input) => resolveUserPath(input),

resolveUserPath('.') calls resolveHomeRelativePath('.') which calls path.resolve('.') — resolving relative to the process CWD. This works fine at plugin registration time, but at service start() time (which runs after the gateway is fully up), the return value comes back as undefined for extension plugins, breaking any file I/O that depends on it.

By contrast, api.rootDir is always correctly set to the plugin's install directory (e.g. /home/user/.openclaw/extensions/clawtalk). The plugin is using api.resolvePath('.') with the reasonable expectation that it returns something rooted in the plugin's own data/install directory — which is what api.rootDir provides.

Fix Action

Workaround

Patching the plugin's build/index.js to use api.resolvePath('.') || api.rootDir as a fallback resolves the issue on the consumer side, but the contract should be fixed in openclaw so extension plugin authors can rely on api.resolvePath working correctly.

PR fix notes

PR #74718: fix(plugins): root api.resolvePath against plugin rootDir for relative paths

Description (problem / solution / changelog)

Summary

Fixes #74676.

Extension plugins calling api.resolvePath('.') inside a service start() handler received undefined because the resolver in createApi was wired directly to resolveUserPath, which resolves . against the process CWD — and returns undefined in the service-start phase for non-bundled plugins. This caused any extension plugin that writes files on startup (e.g. clawtalk's WebSocket log) to throw:

TypeError: The "path" argument must be of type string. Received undefined

The SDK docs state that api.resolvePath(input) resolves relative to the plugin root, but the wiring in createApi (registry.ts) never honoured that contract for extension plugins.

Root cause

In src/plugins/registry.ts, createApi wired resolvePath as:

resolvePath: (input: string) => resolveUserPath(input),

resolveUserPath('.') calls resolveHomeRelativePath('.') which calls path.resolve('.') — the process CWD. For non-bundled extension plugins, the service start() lifecycle runs after gateway startup in a context where this returns undefined rather than a usable path. Meanwhile, api.rootDir is always correctly set to the plugin's install directory (e.g. ~/.openclaw/extensions/clawtalk) — so the information needed to honour the contract was available, just never used.

Code review findings

Several approaches were explored and rejected before landing on the final fix:

Attempt 1 — activation.onStartup + plugins.allow: The doctor warning about implicit startup loading led to adding activation.onStartup: true in the plugin manifest and adding clawtalk to plugins.allow. This fixed the deprecation warning but did not resolve the undefined path — the resolvePath bug is independent of how the plugin is discovered.

Attempt 2 — patching the compiled plugin: A local workaround of api.resolvePath('.') || api.rootDir in clawtalk/build/index.js confirmed the theory — api.rootDir is always correct — and unblocked the service, but the contract should be fixed upstream.

Test approach: CJS plugin + globalThis: The first test attempt wrote a CJS plugin that assigned api.resolvePath('.') to globalThis inside service.start() and read it back from the test. This failed silently because vitest runs test files in worker threads; globalThis inside a dynamically require()d CJS module is not the same object as globalThis in the test worker.

Test approach: registerCommand as a value carrier: Commands run synchronously during register() and their handlers are stored on registry.commands, making them accessible from the test without cross-worker sharing. This approach failed because registerCommand requires a description field (caught by validatePluginCommandDefinition), and even after adding it, the global pluginCommands Map — which persists across tests in the same process — caused silent registration failures due to name conflicts from prior test runs. resetPluginRuntimeStateForTest() does not clear pluginCommands.

Final approach — extract and test the helper directly: The fix extracts the resolver logic into resolvePluginPath(input, rootDir), exports it for testing, and tests it directly in plugin-registry.test.ts with pure synchronous assertions. No plugin loading machinery, no cross-worker globals, no global command registry. createApi is reduced to a one-liner delegate.

Changes

src/plugins/registry.ts

  • Extract resolvePluginPath(input, rootDir) helper (exported for unit testing) that:
    • passes empty input through resolveUserPath unchanged (preserves existing "" return)
    • passes absolute paths and ~-relative paths through resolveUserPath unchanged
    • resolves all other relative paths (including .) under record.rootDir, matching the documented contract
  • Wire createApi's resolvePath to resolvePluginPath(input, record.rootDir) (one-liner)

src/plugins/plugin-registry.test.ts

  • Add describe("resolvePluginPath") with 6 unit tests covering: . → rootDir, relative sub-path, absolute pass-through, ~ expansion, empty string, and undefined-rootDir fallback

CHANGELOG.md

  • Entry in Unreleased → Fixes

Test plan

  • pnpm exec oxfmt --check --threads=1 src/plugins/registry.ts src/plugins/plugin-registry.test.ts src/plugins/loader.test.ts CHANGELOG.md — clean
  • vitest run src/plugins/loader.test.ts src/plugins/services.test.ts src/plugins/plugin-registry.test.ts — 155 passed, 0 failed

🤖 Generated with Claude Code

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/plugins/loader.test.ts (modified, +44/-0)
  • src/plugins/loader.ts (modified, +5/-2)
  • src/plugins/plugin-paths.ts (added, +18/-0)
  • src/plugins/plugin-registry.test.ts (modified, +37/-0)
  • src/plugins/registry.ts (modified, +2/-2)
  • src/plugins/setup-registry.ts (modified, +2/-1)

Code Example

[clawtalk] ClawTalk service start failed: The "path" argument must be of type string. Received undefined

---

resolvePath: (input) => resolveUserPath(input),

---

resolvePath: (input) => {
  if (!input || input === '.') return record.rootDir;
  return resolveUserPath(input);
},
RAW_BUFFERClick to expand / collapse

Description

api.resolvePath('.') returns undefined when called from within a plugin's registered service start() function for extension-based (non-stock) plugins installed under ~/.openclaw/extensions/. This causes any extension plugin that uses api.resolvePath for file I/O during service startup to fail.

Environment

  • OpenClaw version: 2026.4.26 / 2026.4.27
  • Plugin: clawtalk v0.2.3 (npm-installed extension plugin)
  • Platform: Linux (Arch)

Steps to Reproduce

  1. Install an extension plugin that calls api.resolvePath('.') inside a service start() handler (e.g. openclaw plugins install npm:clawtalk)
  2. Start the gateway
  3. Observe the service start failure in logs

Error

[clawtalk] ClawTalk service start failed: The "path" argument must be of type string. Received undefined

The error originates from path.dirname(undefined) inside the plugin, where undefined was the result of api.resolvePath('.').

Root Cause Analysis

In loader-CPsG_3Jg.js, createApi() wires resolvePath as:

resolvePath: (input) => resolveUserPath(input),

resolveUserPath('.') calls resolveHomeRelativePath('.') which calls path.resolve('.') — resolving relative to the process CWD. This works fine at plugin registration time, but at service start() time (which runs after the gateway is fully up), the return value comes back as undefined for extension plugins, breaking any file I/O that depends on it.

By contrast, api.rootDir is always correctly set to the plugin's install directory (e.g. /home/user/.openclaw/extensions/clawtalk). The plugin is using api.resolvePath('.') with the reasonable expectation that it returns something rooted in the plugin's own data/install directory — which is what api.rootDir provides.

Suggested Fix

For extension plugins, resolvePath should resolve relative to record.rootDir (the plugin's install directory), not the process CWD:

resolvePath: (input) => {
  if (!input || input === '.') return record.rootDir;
  return resolveUserPath(input);
},

Or at minimum, resolvePath('.') should never return undefined — a fallback to record.rootDir would be safe and correct.

Workaround

Patching the plugin's build/index.js to use api.resolvePath('.') || api.rootDir as a fallback resolves the issue on the consumer side, but the contract should be fixed in openclaw so extension plugin authors can rely on api.resolvePath working correctly.

extent analysis

TL;DR

The most likely fix is to modify the resolvePath function in OpenClaw to resolve relative to the plugin's install directory (record.rootDir) instead of the process CWD.

Guidance

  • Verify that the issue is specific to extension plugins installed under ~/.openclaw/extensions/ and that the api.resolvePath function is being called from within a service start() function.
  • Check the loader-CPsG_3Jg.js file to confirm that the createApi() function is wiring resolvePath as (input) => resolveUserPath(input).
  • Consider implementing the suggested fix by modifying the resolvePath function to use record.rootDir as a fallback when input is '.' or an empty string.
  • As a temporary workaround, plugin authors can use api.resolvePath('.') || api.rootDir to ensure that file I/O operations work correctly.

Example

resolvePath: (input) => {
  if (!input || input === '.') return record.rootDir;
  return resolveUserPath(input);
}

Notes

The suggested fix assumes that record.rootDir is always correctly set to the plugin's install directory. If this is not the case, additional debugging may be necessary.

Recommendation

Apply the workaround by using api.resolvePath('.') || api.rootDir in plugin code, as this provides a safe and correct fallback until the underlying issue is fixed in OpenClaw.

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 [Bug]: api.resolvePath returns undefined for extension plugins during service start [1 pull requests, 3 comments, 3 participants]