openclaw - ✅(Solved) Fix [Bug]: textTransforms from plugins never applied — loadPluginRuntime() always returns null in main process [1 pull requests, 1 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#64921Fetched 2026-04-12 13:26:16
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Participants
Timeline (top)
commented ×1cross-referenced ×1referenced ×1

Plugin-registered textTransforms (via api.registerTextTransforms()) are never applied to agent prompts or system prompts in the main gateway process. The transforms are registered successfully (confirmed by log output), but the reading side always returns empty.

Error Message

import { createRequire } from "node:module"; const require = createRequire("/path/to/openclaw/dist/provider-runtime-<hash>.js"); require("./runtime.js"); // → Error: Cannot find module './runtime.js'

Root Cause

resolveRuntimeTextTransforms() in src/plugins/text-transforms.runtime.ts relies entirely on loadPluginRuntime() to access the active plugin registry:

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  const registry = loadPluginRuntime()?.getActivePluginRegistry();
  // ...
}

loadPluginRuntime() tries to require("./runtime.js") (and "./runtime.ts"). In the main openclaw process, dist/runtime.js does not exist — only the content-hashed dist/runtime-<hash>.js does. So require("./runtime.js") always throws, the catch swallows it, and loadPluginRuntime() returns null.

Result: resolveRuntimeTextTransforms() always returns undefined, and backendResolved.textTransforms is always empty.

Fix Action

Fix

In src/plugins/text-transforms.runtime.ts, add a direct import of getActivePluginRegistry from the runtime module. Use it as the primary path (works in the main bundle), and keep loadPluginRuntime() as a fallback (for external plugin contexts):

import { getActivePluginRegistry } from "./runtime.js"; // resolved correctly at build time

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  // Direct import works in main process (bundled together)
  // loadPluginRuntime() fallback works in external plugin contexts
  const registry = getActivePluginRegistry() ?? loadPluginRuntime()?.getActivePluginRegistry();
  const pluginTextTransforms = Array.isArray(registry?.textTransforms)
    ? registry.textTransforms.map((entry) => entry.transforms)
    : [];
  return mergePluginTextTransforms(...pluginTextTransforms);
}

PR fix notes

PR #64924: fix: textTransforms from plugins now applied in main process

Description (problem / solution / changelog)

Summary

textTransforms from plugins (via api.registerTextTransforms()) were never applied to agent prompts or system prompts in the main gateway process. The transforms were registered successfully but the reading side always returned empty.

Root Cause

resolveRuntimeTextTransforms() in src/plugins/text-transforms.runtime.ts relied entirely on loadPluginRuntime() to access the active plugin registry. loadPluginRuntime() uses require("./runtime.js") which always fails in the main process because dist/runtime.js doesn't exist - only dist/runtime-<hash>.js exists.

Fix

Added direct import of getActivePluginRegistry from ./runtime.js as the primary path, with loadPluginRuntime() kept as a fallback for external plugin contexts.

const registry = getActivePluginRegistry() ?? loadPluginRuntime()?.getActivePluginRegistry();

This ensures:

  • Direct import works in main process (bundled together)
  • loadPluginRuntime() fallback works in external plugin contexts

Issue

Fixes #64921

With AI assist: MiniMax-M2.7

Changed files

  • extensions/browser/src/browser/chrome-wsl-display.test.ts (added, +47/-0)
  • extensions/browser/src/browser/chrome.ts (modified, +3/-0)
  • src/auto-reply/reply/session-reset-model.test.ts (modified, +24/-0)
  • src/auto-reply/reply/session-reset-model.ts (modified, +10/-0)
  • src/plugins/text-transforms.runtime.ts (modified, +2/-1)
  • ui/src/ui/views/config-form.node.ts (modified, +3/-3)

Code Example

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  const registry = loadPluginRuntime()?.getActivePluginRegistry();
  // ...
}

---

# Plugin registers transforms successfully:
[plugins] [my-plugin:transforms] text transforms registered (input: 2, output: 0)

# But system prompt / prompts still contain the un-replaced strings

---

import { createRequire } from "node:module";
const require = createRequire("/path/to/openclaw/dist/provider-runtime-<hash>.js");
require("./runtime.js"); // → Error: Cannot find module './runtime.js'

---

import { getActivePluginRegistry } from "./runtime.js"; // resolved correctly at build time

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  // Direct import works in main process (bundled together)
  // loadPluginRuntime() fallback works in external plugin contexts
  const registry = getActivePluginRegistry() ?? loadPluginRuntime()?.getActivePluginRegistry();
  const pluginTextTransforms = Array.isArray(registry?.textTransforms)
    ? registry.textTransforms.map((entry) => entry.transforms)
    : [];
  return mergePluginTextTransforms(...pluginTextTransforms);
}
RAW_BUFFERClick to expand / collapse

Summary

Plugin-registered textTransforms (via api.registerTextTransforms()) are never applied to agent prompts or system prompts in the main gateway process. The transforms are registered successfully (confirmed by log output), but the reading side always returns empty.

Root Cause

resolveRuntimeTextTransforms() in src/plugins/text-transforms.runtime.ts relies entirely on loadPluginRuntime() to access the active plugin registry:

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  const registry = loadPluginRuntime()?.getActivePluginRegistry();
  // ...
}

loadPluginRuntime() tries to require("./runtime.js") (and "./runtime.ts"). In the main openclaw process, dist/runtime.js does not exist — only the content-hashed dist/runtime-<hash>.js does. So require("./runtime.js") always throws, the catch swallows it, and loadPluginRuntime() returns null.

Result: resolveRuntimeTextTransforms() always returns undefined, and backendResolved.textTransforms is always empty.

Evidence

# Plugin registers transforms successfully:
[plugins] [my-plugin:transforms] text transforms registered (input: 2, output: 0)

# But system prompt / prompts still contain the un-replaced strings

Confirmed with Node.js:

import { createRequire } from "node:module";
const require = createRequire("/path/to/openclaw/dist/provider-runtime-<hash>.js");
require("./runtime.js"); // → Error: Cannot find module './runtime.js'

Why loadPluginRuntime() exists

The require("./runtime.js") pattern makes sense for external plugins (separate npm packages) that don't know the content-hash of the runtime file. They need a stable path to cross the module boundary and share singleton state.

However, in the main process, provider-runtime-<hash>.js is already bundled together with the runtime module and can import from it directly via ES module imports.

Fix

In src/plugins/text-transforms.runtime.ts, add a direct import of getActivePluginRegistry from the runtime module. Use it as the primary path (works in the main bundle), and keep loadPluginRuntime() as a fallback (for external plugin contexts):

import { getActivePluginRegistry } from "./runtime.js"; // resolved correctly at build time

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  // Direct import works in main process (bundled together)
  // loadPluginRuntime() fallback works in external plugin contexts
  const registry = getActivePluginRegistry() ?? loadPluginRuntime()?.getActivePluginRegistry();
  const pluginTextTransforms = Array.isArray(registry?.textTransforms)
    ? registry.textTransforms.map((entry) => entry.transforms)
    : [];
  return mergePluginTextTransforms(...pluginTextTransforms);
}

Affected versions

  • Confirmed on 2026.4.10 (dist: 44e5b62)
  • Root cause present in current main branch (src/plugins/text-transforms.runtime.ts)

Impact

All plugins using api.registerTextTransforms() — the registered transforms are silently ignored. Affects prompt sanitization, obfuscation of special tags (e.g. [[reply_to_current]]), and any text replacement configured via plugin textTransforms config.

extent analysis

TL;DR

Update src/plugins/text-transforms.runtime.ts to use a direct import of getActivePluginRegistry as the primary path and keep loadPluginRuntime() as a fallback.

Guidance

  • Verify that the getActivePluginRegistry function is correctly exported from the runtime module and can be imported in src/plugins/text-transforms.runtime.ts.
  • Update the resolveRuntimeTextTransforms function to use the direct import of getActivePluginRegistry as the primary path, and keep loadPluginRuntime() as a fallback for external plugin contexts.
  • Test the updated resolveRuntimeTextTransforms function to ensure it correctly resolves the registered text transforms.
  • Validate that the text transforms are applied correctly to agent prompts and system prompts in the main gateway process.

Example

import { getActivePluginRegistry } from "./runtime.js";

export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined {
  const registry = getActivePluginRegistry() ?? loadPluginRuntime()?.getActivePluginRegistry();
  // ...
}

Notes

This fix assumes that the getActivePluginRegistry function is correctly exported from the runtime module and can be imported in src/plugins/text-transforms.runtime.ts. If this is not the case, additional changes may be required.

Recommendation

Apply the workaround by updating src/plugins/text-transforms.runtime.ts to use a direct import of getActivePluginRegistry as the primary path and keep loadPluginRuntime() as a fallback, as this should resolve the issue without requiring an upgrade to a fixed version.

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