openclaw - 💡(How to fix) Fix [Show and Tell] canon-guardian: selective workspace canon re-injection on model switch / native fallback / restart [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#75750Fetched 2026-05-02 05:30:47
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
2
Author
Timeline (top)
commented ×2closed ×1cross-referenced ×1mentioned ×1

Error Message

// Canon Guardian — selective workspace canon re-injection for OpenClaw 2026.4.x // // Hook: before_prompt_build — returns prependSystemContext so the canon // stays in the cache-eligible portion of the system prompt. // // State: in-memory Map keyed by sessionId. Clears on gateway restart, which // is intentional — the next turn after restart re-injects.

import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { promises as fs } from "node:fs"; import path from "node:path";

const DEFAULTS = { canonFiles: ["AGENTS.md"], injectionInterval: 50, restartIdleMs: 6 * 60 * 60 * 1000, logInjections: true, };

function readConfig(api) { const cfg = api.pluginConfig || {}; return { canonFiles: Array.isArray(cfg.canonFiles) && cfg.canonFiles.length ? cfg.canonFiles : DEFAULTS.canonFiles, injectionInterval: typeof cfg.injectionInterval === "number" ? cfg.injectionInterval : DEFAULTS.injectionInterval, restartIdleMs: typeof cfg.restartIdleMs === "number" ? cfg.restartIdleMs : DEFAULTS.restartIdleMs, logInjections: typeof cfg.logInjections === "boolean" ? cfg.logInjections : DEFAULTS.logInjections, }; }

export default definePluginEntry({ id: "canon-guardian", name: "Canon Guardian", description: "Selective workspace canon re-injection on model switch, new session, gateway restart, and periodic intervals.", register(api) { if (api.registrationMode !== "full") return;

const cfg = readConfig(api);
const log = api.logger;

// Workspace-keyed cache so multi-agent gateways do not cross-pollinate.
const canonCache = new Map();
// sessionId -> { lastModelKey, turnCount, lastInjectMs }
const sessionState = new Map();

async function loadCanon(workspaceDir) {
  if (!workspaceDir) return "";
  const cached = canonCache.get(workspaceDir);
  if (cached) return cached;

  const parts = await Promise.all(
    cfg.canonFiles.map(async (rel) => {
      const safeRel = String(rel).replace(/^[/\\]+/, "");
      const full = path.join(workspaceDir, safeRel);
      if (!full.startsWith(workspaceDir)) {
        return `<!-- canon-guardian: refused unsafe path "${rel}" -->`;
      }
      try {
        const text = await fs.readFile(full, "utf8");
        return `<!-- canon-guardian: ${safeRel} -->\n${text}`;
      } catch {
        return `<!-- canon-guardian: ${safeRel} not found -->`;
      }
    }),
  );
  const joined = parts.join("\n\n---\n\n");
  canonCache.set(workspaceDir, joined);
  return joined;
}

api.on("before_prompt_build", async (_event, ctx) => {
  try {
    const sessionId = ctx?.sessionId;
    const modelId = ctx?.modelId || "unknown";
    const providerId = ctx?.modelProviderId || "unknown";
    const workspaceDir = ctx?.workspaceDir;
    if (!sessionId || !workspaceDir) return;

    const modelKey = `${providerId}:${modelId}`;
    const now = Date.now();
    const prev = sessionState.get(sessionId);
    const turnCount = (prev?.turnCount || 0) + 1;

    const isNewSession = !prev;
    const modelSwitched = !!prev && prev.lastModelKey !== modelKey;
    const periodic =
      turnCount > 1 &&
      cfg.injectionInterval > 0 &&
      turnCount % cfg.injectionInterval === 0;
    const restartDetected =
      !!prev &&
      cfg.restartIdleMs > 0 &&
      now - (prev.lastInjectMs || 0) > cfg.restartIdleMs;

    const reason = isNewSession
      ? "new-session"
      : modelSwitched
        ? "model-switch"
        : restartDetected
          ? "restart-or-idle"
          : periodic
            ? `periodic-${cfg.injectionInterval}`
            : null;

    sessionState.set(sessionId, {
      lastModelKey: modelKey,
      turnCount,
      lastInjectMs: reason ? now : prev?.lastInjectMs || 0,
    });

    if (!reason) return;

    const canon = await loadCanon(workspaceDir);
    if (!canon) return;

    if (cfg.logInjections) {
      log.info(
        `[canon-guardian] re-injecting canon (reason=${reason}, turn=${turnCount}, model=${modelKey})`,
      );
    }

    return { prependSystemContext: canon };
  } catch (err) {
    log.error(`[canon-guardian] hook error: ${err?.message || String(err)}`);
    // Fail gracefully: return nothing (no injection) rather than crash the build.
  }
});

log.info("[canon-guardian] loaded");

}, });

Root Cause

The canon-guardian plugin slots in cleanly because the platform already gave us the right shape to slot into.

Fix Action

Fix / Workaround

Result: the model-that-actually-answers wakes up partially blind to operating procedures and persona — the "session suddenly feels different" failure mode. (We filed this as #65824 item 1; this plugin is our local mitigation while we wait on a native solution.)

Code Example

{
  "id": "canon-guardian",
  "name": "Canon Guardian",
  "version": "1.0.0",
  "description": "Selective canon re-injection on model switch, new session, gateway restart, and periodic intervals.",
  "main": "./index.js",
  "type": "module"
}

---

// Canon Guardian — selective workspace canon re-injection for OpenClaw 2026.4.x
//
// Hook: `before_prompt_build` — returns `prependSystemContext` so the canon
// stays in the cache-eligible portion of the system prompt.
//
// State: in-memory Map keyed by sessionId. Clears on gateway restart, which
// is intentional — the next turn after restart re-injects.

import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { promises as fs } from "node:fs";
import path from "node:path";

const DEFAULTS = {
  canonFiles: ["AGENTS.md"],
  injectionInterval: 50,
  restartIdleMs: 6 * 60 * 60 * 1000,
  logInjections: true,
};

function readConfig(api) {
  const cfg = api.pluginConfig || {};
  return {
    canonFiles: Array.isArray(cfg.canonFiles) && cfg.canonFiles.length
      ? cfg.canonFiles : DEFAULTS.canonFiles,
    injectionInterval: typeof cfg.injectionInterval === "number"
      ? cfg.injectionInterval : DEFAULTS.injectionInterval,
    restartIdleMs: typeof cfg.restartIdleMs === "number"
      ? cfg.restartIdleMs : DEFAULTS.restartIdleMs,
    logInjections: typeof cfg.logInjections === "boolean"
      ? cfg.logInjections : DEFAULTS.logInjections,
  };
}

export default definePluginEntry({
  id: "canon-guardian",
  name: "Canon Guardian",
  description:
    "Selective workspace canon re-injection on model switch, new session, gateway restart, and periodic intervals.",
  register(api) {
    if (api.registrationMode !== "full") return;

    const cfg = readConfig(api);
    const log = api.logger;

    // Workspace-keyed cache so multi-agent gateways do not cross-pollinate.
    const canonCache = new Map();
    // sessionId -> { lastModelKey, turnCount, lastInjectMs }
    const sessionState = new Map();

    async function loadCanon(workspaceDir) {
      if (!workspaceDir) return "";
      const cached = canonCache.get(workspaceDir);
      if (cached) return cached;

      const parts = await Promise.all(
        cfg.canonFiles.map(async (rel) => {
          const safeRel = String(rel).replace(/^[/\\]+/, "");
          const full = path.join(workspaceDir, safeRel);
          if (!full.startsWith(workspaceDir)) {
            return `<!-- canon-guardian: refused unsafe path "${rel}" -->`;
          }
          try {
            const text = await fs.readFile(full, "utf8");
            return `<!-- canon-guardian: ${safeRel} -->\n${text}`;
          } catch {
            return `<!-- canon-guardian: ${safeRel} not found -->`;
          }
        }),
      );
      const joined = parts.join("\n\n---\n\n");
      canonCache.set(workspaceDir, joined);
      return joined;
    }

    api.on("before_prompt_build", async (_event, ctx) => {
      try {
        const sessionId = ctx?.sessionId;
        const modelId = ctx?.modelId || "unknown";
        const providerId = ctx?.modelProviderId || "unknown";
        const workspaceDir = ctx?.workspaceDir;
        if (!sessionId || !workspaceDir) return;

        const modelKey = `${providerId}:${modelId}`;
        const now = Date.now();
        const prev = sessionState.get(sessionId);
        const turnCount = (prev?.turnCount || 0) + 1;

        const isNewSession = !prev;
        const modelSwitched = !!prev && prev.lastModelKey !== modelKey;
        const periodic =
          turnCount > 1 &&
          cfg.injectionInterval > 0 &&
          turnCount % cfg.injectionInterval === 0;
        const restartDetected =
          !!prev &&
          cfg.restartIdleMs > 0 &&
          now - (prev.lastInjectMs || 0) > cfg.restartIdleMs;

        const reason = isNewSession
          ? "new-session"
          : modelSwitched
            ? "model-switch"
            : restartDetected
              ? "restart-or-idle"
              : periodic
                ? `periodic-${cfg.injectionInterval}`
                : null;

        sessionState.set(sessionId, {
          lastModelKey: modelKey,
          turnCount,
          lastInjectMs: reason ? now : prev?.lastInjectMs || 0,
        });

        if (!reason) return;

        const canon = await loadCanon(workspaceDir);
        if (!canon) return;

        if (cfg.logInjections) {
          log.info(
            `[canon-guardian] re-injecting canon (reason=${reason}, turn=${turnCount}, model=${modelKey})`,
          );
        }

        return { prependSystemContext: canon };
      } catch (err) {
        log.error(`[canon-guardian] hook error: ${err?.message || String(err)}`);
        // Fail gracefully: return nothing (no injection) rather than crash the build.
      }
    });

    log.info("[canon-guardian] loaded");
  },
});

---

{
  "plugins": {
    "allow": ["canon-guardian"],
    "search": ["/Users/<you>/.openclaw/plugins/canon-guardian"],
    "config": {
      "canon-guardian": {
        "canonFiles": ["AGENTS.md", "SOUL.md", "OPERATING_RULES.md"],
        "injectionInterval": 50,
        "restartIdleMs": 21600000,
        "logInjections": true
      }
    }
  }
}

---

[canon-guardian] re-injecting canon (reason=new-session, turn=1, model=anthropic:claude-opus-4-7)
↪️ Model Fallback: claude-haiku-4-5
[canon-guardian] re-injecting canon (reason=model-switch, turn=2, model=anthropic:claude-haiku-4-5)
RAW_BUFFERClick to expand / collapse

[Show and Tell] canon-guardian: selective workspace canon re-injection on model switch / native fallback / restart

Author: @smonett (intensive daily user since 2026.2) Audited against: OpenClaw 2026.4.27 (verified against dist/ package internals — line refs below) Companion to: #65824 item 1 ("Silent model fallback + no system-prompt re-injection") Status: Working in production. Sharing as a reference implementation, not a feature ask.


The problem

Long-lived OpenClaw sessions running on agents.defaults.contextInjection: "continuation-skip" skip re-injecting workspace bootstrap (AGENTS.md, SOUL.md, etc.) on every turn — saves a lot of tokens.

The trade-off shows up during native model fallback: when the primary 402s / rate-limits and the fallback chain fires, the fallback model inherits the conversation transcript but not the workspace bootstrap. Same for manual /model switches mid-session, and for sessions that drift past the 6+ hour mark where we'd want a sanity refresh.

Result: the model-that-actually-answers wakes up partially blind to operating procedures and persona — the "session suddenly feels different" failure mode. (We filed this as #65824 item 1; this plugin is our local mitigation while we wait on a native solution.)

What canon-guardian does

A small native plugin that re-injects the workspace canon only on events that justify it:

TriggerReason stringDetection
New session (first turn)new-sessionno prior state for sessionId
Model switch (manual or fallback)model-switch${providerId}:${modelId} changed since last turn
Long idle / restartrestart-or-idlenow - lastInjectMs > restartIdleMs (default 6h)
Periodic safety netperiodic-50turnCount % 50 === 0

If none of those fire, the hook returns nothing. On a quiet turn the plugin's overhead is one Map lookup.

Why we picked before_prompt_build

We tried llm_input first based on outside advice. That was wrong, and the cost was a couple of bad turns where we thought it was working but our fallback model was still booting blind. Receipts:

  • dist/hook-runner-global-BmRBtwY4.js lines ~140-160 — mergeBeforePromptBuild is the only merge function that exposes prependSystemContext, the field that ends up in the cache-eligible prefix of the system prompt.
  • dist/status-DjPbn_Lx.js line ~111 — runtime explicitly warns plugin authors: "still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve / before_prompt_build for new work." That's the maintainer-blessed direction; we followed it.
  • dist/extensions/active-memory/index.js line ~1190 — the first-party active-memory extension uses before_prompt_build with the same try/catch shape. Same pattern, same hook, validated upstream.

Sharing the misstep here so the next plugin author doesn't repeat it. If there's appetite for a docs note explicitly steering people away from llm_input for system-prompt mutation, happy to PR one.

Multi-model behavior (audited)

  • dist/lifecycle-hook-helpers-g--XRrcl.js lines 4-18 — buildAgentHookContext populates modelProviderId + modelId provider-agnostically. Every provider flows through the same builder.
  • The plugin keys on the composite ${providerId}:${modelId}, so all the following count as a model-switch event:
    • Anthropic ↔ xAI / Grok
    • Anthropic ↔ Google / Gemini
    • Anthropic ↔ OpenAI
    • Same provider, different tier (e.g. anthropic:claude-opus-4-7anthropic:claude-haiku-4-5)
  • On native fallback specifically: the hook context reflects the model that's about to answer, not the originally-configured primary. That's the behavior we want — re-inject for whoever is on the call.

How it composes with other plugins

mergeBeforePromptBuild (same file as above) concatenates prependSystemContext segments from every registered hook rather than overwriting. So canon-guardian composes cleanly with active-memory and any other plugin that uses the same hook surface — order is registration order, separator is the runtime's concatOptionalTextSegments joiner.

When this beats contextInjection: "always"

contextInjection: "always" is the brute-force alternative: re-inject canon on every turn. That works but pays the canon's input-token cost on every turn. On a 50KB canon × 200-turn session at Opus rates, the difference is measurable.

canon-guardian re-injects on roughly 2–4% of turns in our usage (new session + occasional fallback + periodic safety net). Same correctness, fraction of the cost.

If your sessions are short or your canon is small, contextInjection: "always" is the simpler answer and we'd recommend that first.

What 4.27 already gets right

  • buildFallbackNotice emits ↪️ Model Fallback: <active> lifecycle events on transition (visible to verbose users)
  • The before_prompt_build contract is well-defined and stable; the mergeBeforePromptBuild semantics (concat, not overwrite) make plugin composition safe by design
  • Default hook timeout (DEFAULT_MODIFYING_HOOK_TIMEOUT_MS_BY_HOOK.before_prompt_build = 15000) is generous enough for canon load even on cold cache

The canon-guardian plugin slots in cleanly because the platform already gave us the right shape to slot into.

Code

Two files. ~140 LOC total.

~/.openclaw/plugins/canon-guardian/openclaw.plugin.json:

{
  "id": "canon-guardian",
  "name": "Canon Guardian",
  "version": "1.0.0",
  "description": "Selective canon re-injection on model switch, new session, gateway restart, and periodic intervals.",
  "main": "./index.js",
  "type": "module"
}

~/.openclaw/plugins/canon-guardian/index.js:

// Canon Guardian — selective workspace canon re-injection for OpenClaw 2026.4.x
//
// Hook: `before_prompt_build` — returns `prependSystemContext` so the canon
// stays in the cache-eligible portion of the system prompt.
//
// State: in-memory Map keyed by sessionId. Clears on gateway restart, which
// is intentional — the next turn after restart re-injects.

import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { promises as fs } from "node:fs";
import path from "node:path";

const DEFAULTS = {
  canonFiles: ["AGENTS.md"],
  injectionInterval: 50,
  restartIdleMs: 6 * 60 * 60 * 1000,
  logInjections: true,
};

function readConfig(api) {
  const cfg = api.pluginConfig || {};
  return {
    canonFiles: Array.isArray(cfg.canonFiles) && cfg.canonFiles.length
      ? cfg.canonFiles : DEFAULTS.canonFiles,
    injectionInterval: typeof cfg.injectionInterval === "number"
      ? cfg.injectionInterval : DEFAULTS.injectionInterval,
    restartIdleMs: typeof cfg.restartIdleMs === "number"
      ? cfg.restartIdleMs : DEFAULTS.restartIdleMs,
    logInjections: typeof cfg.logInjections === "boolean"
      ? cfg.logInjections : DEFAULTS.logInjections,
  };
}

export default definePluginEntry({
  id: "canon-guardian",
  name: "Canon Guardian",
  description:
    "Selective workspace canon re-injection on model switch, new session, gateway restart, and periodic intervals.",
  register(api) {
    if (api.registrationMode !== "full") return;

    const cfg = readConfig(api);
    const log = api.logger;

    // Workspace-keyed cache so multi-agent gateways do not cross-pollinate.
    const canonCache = new Map();
    // sessionId -> { lastModelKey, turnCount, lastInjectMs }
    const sessionState = new Map();

    async function loadCanon(workspaceDir) {
      if (!workspaceDir) return "";
      const cached = canonCache.get(workspaceDir);
      if (cached) return cached;

      const parts = await Promise.all(
        cfg.canonFiles.map(async (rel) => {
          const safeRel = String(rel).replace(/^[/\\]+/, "");
          const full = path.join(workspaceDir, safeRel);
          if (!full.startsWith(workspaceDir)) {
            return `<!-- canon-guardian: refused unsafe path "${rel}" -->`;
          }
          try {
            const text = await fs.readFile(full, "utf8");
            return `<!-- canon-guardian: ${safeRel} -->\n${text}`;
          } catch {
            return `<!-- canon-guardian: ${safeRel} not found -->`;
          }
        }),
      );
      const joined = parts.join("\n\n---\n\n");
      canonCache.set(workspaceDir, joined);
      return joined;
    }

    api.on("before_prompt_build", async (_event, ctx) => {
      try {
        const sessionId = ctx?.sessionId;
        const modelId = ctx?.modelId || "unknown";
        const providerId = ctx?.modelProviderId || "unknown";
        const workspaceDir = ctx?.workspaceDir;
        if (!sessionId || !workspaceDir) return;

        const modelKey = `${providerId}:${modelId}`;
        const now = Date.now();
        const prev = sessionState.get(sessionId);
        const turnCount = (prev?.turnCount || 0) + 1;

        const isNewSession = !prev;
        const modelSwitched = !!prev && prev.lastModelKey !== modelKey;
        const periodic =
          turnCount > 1 &&
          cfg.injectionInterval > 0 &&
          turnCount % cfg.injectionInterval === 0;
        const restartDetected =
          !!prev &&
          cfg.restartIdleMs > 0 &&
          now - (prev.lastInjectMs || 0) > cfg.restartIdleMs;

        const reason = isNewSession
          ? "new-session"
          : modelSwitched
            ? "model-switch"
            : restartDetected
              ? "restart-or-idle"
              : periodic
                ? `periodic-${cfg.injectionInterval}`
                : null;

        sessionState.set(sessionId, {
          lastModelKey: modelKey,
          turnCount,
          lastInjectMs: reason ? now : prev?.lastInjectMs || 0,
        });

        if (!reason) return;

        const canon = await loadCanon(workspaceDir);
        if (!canon) return;

        if (cfg.logInjections) {
          log.info(
            `[canon-guardian] re-injecting canon (reason=${reason}, turn=${turnCount}, model=${modelKey})`,
          );
        }

        return { prependSystemContext: canon };
      } catch (err) {
        log.error(`[canon-guardian] hook error: ${err?.message || String(err)}`);
        // Fail gracefully: return nothing (no injection) rather than crash the build.
      }
    });

    log.info("[canon-guardian] loaded");
  },
});

Configuration

Add to openclaw.json:

{
  "plugins": {
    "allow": ["canon-guardian"],
    "search": ["/Users/<you>/.openclaw/plugins/canon-guardian"],
    "config": {
      "canon-guardian": {
        "canonFiles": ["AGENTS.md", "SOUL.md", "OPERATING_RULES.md"],
        "injectionInterval": 50,
        "restartIdleMs": 21600000,
        "logInjections": true
      }
    }
  }
}

Restart the gateway. Look for [canon-guardian] loaded on startup, and [canon-guardian] re-injecting canon (reason=...) on the first turn of each session.

Sample output during a primary→fallback transition:

[canon-guardian] re-injecting canon (reason=new-session, turn=1, model=anthropic:claude-opus-4-7)
↪️ Model Fallback: claude-haiku-4-5
[canon-guardian] re-injecting canon (reason=model-switch, turn=2, model=anthropic:claude-haiku-4-5)

Operational notes

  • Cache friendliness: prepending via prependSystemContext puts canon at the head of the system prompt, where Anthropic / OpenAI / Google all cache stable prefixes. Re-inject turns are roughly one cache-write, not a full re-encode. (Anthropic specifically: stable system-prompt prefix → 90% cost reduction on cached tokens.)
  • Workspace path safety: rejects paths that escape workspaceDir via prefix check; symlinks not followed beyond the workspace root.
  • Failure isolation: entire hook body in try/catch; on error logs and returns no injection rather than crashing the prompt build. Same shape as active-memory.
  • Memory profile: per-session state is ~50 bytes; canon cache is one entry per workspace and is invalidated on gateway restart.
  • Composition: prependSystemContext from multiple plugins is concatenated (mergeBeforePromptBuild), not overwritten. Safe to run alongside active-memory or other prepend-context plugins.

Acknowledged trade-offs

  • In-memory state. Survives across turns within a process, dies on gateway restart. That's intentional but it does mean the first turn after a restart re-injects even on a continuation — by design, but worth flagging.
  • Periodic safety net. Default 50 turns is empirical, not theoretical. If you've never seen drift in a long session, you can disable periodic by setting injectionInterval: 0.
  • Workspace-dir cache. Caches canon file content in memory; if you vi AGENTS.md mid-session the change isn't picked up until restart. Acceptable for our usage; trivially defeatable by adding an mtime check if needed.

Questions for the maintainers

  1. Is there appetite for canon re-injection becoming a native option on fallback? Something like agents.defaults.fallback.reinjectBootstrap: true would obsolete this plugin and we'd happily retire it. (Captured in #65824 item 1.)
  2. Would a docs note explicitly steering people away from llm_input for system-prompt mutation be welcome? Happy to draft a PR if so. The runtime warning on before_agent_start is excellent; an analogous "this is the right hook for X" note in plugin docs would have saved us the misstep.

— Posted from a 2026.4.27 install on macOS arm64. Thanks to the maintainers for the clean hook surface — once we found before_prompt_build, the rest fell out of the design.

extent analysis

TL;DR

To address the issue of selective workspace canon re-injection on model switch, native fallback, or restart in OpenClaw, implement the canon-guardian plugin, which re-injects the workspace canon only on specific events, such as new sessions, model switches, or periodic intervals.

Guidance

  • The canon-guardian plugin uses the before_prompt_build hook to re-inject the workspace canon, ensuring it stays in the cache-eligible portion of the system prompt.
  • The plugin's configuration can be customized in openclaw.json to specify canon files, injection intervals, and logging options.
  • To verify the plugin's effectiveness, look for [canon-guardian] loaded on startup and [canon-guardian] re-injecting canon (reason=...) on the first turn of each session.
  • Consider the trade-offs of using the canon-guardian plugin, such as in-memory state and periodic safety nets, and adjust the configuration accordingly.

Example

The provided index.js code snippet demonstrates how to implement the canon-guardian plugin, including loading canon files, handling model switches, and logging injections.

Notes

The canon-guardian plugin is designed to work with OpenClaw 2026.4.x and may not be compatible with other versions. Additionally, the plugin's behavior may vary depending on the specific use case and configuration.

Recommendation

Apply the canon-guardian plugin as a workaround to address the issue of selective workspace canon re-injection, as it provides a flexible and customizable solution.

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 - 💡(How to fix) Fix [Show and Tell] canon-guardian: selective workspace canon re-injection on model switch / native fallback / restart [2 comments, 2 participants]