Skip to content

dm-agent-sync.ts: silent failures everywhere — .nothrow() on every WP-CLI call, swallowing catch, and isUserOverride() trips on default model #114

@chubes4

Description

@chubes4

Summary

dm-agent-sync.ts swallows every failure mode silently — no log, no error, no surface. Combined with #112 / #113 this is why context-loss bugs go undetected for weeks. Three independent silent-failure paths in 120 lines:

  1. Every WP-CLI call uses .quiet().nothrow() — failures produce empty output that's parsed as "no agents".
  2. The whole config() callback is wrapped in try { ... } catch { /* silent */ } (lines 46–166).
  3. isUserOverride() (line 178) returns true if the agent block has any model string — including the default anthropic/claude-opus-4-7 value emitted into orphaned agent.build shells (see repair-opencode-json.py: orphaned empty agent.build/plan blocks survive #60 prompt migration and silently break dm-agent-sync #112). This third path is the most surprising: it makes the override check fire for blocks the user never customized.

Repro

Path 1: WP-CLI silently fails

  1. Break WP-CLI in any way (DB down, plugin error, missing --allow-root privileges, multisite --url required, even a deprecation notice that pollutes JSON output).
  2. Start an OpenCode session.
  3. wp datamachine memory compose and wp datamachine agents list both .nothrow() — no error reaches OpenCode logs.
  4. The regex agentsRaw.match(/\[[\s\S]*\]/) returns null on empty output → plugin returns silently at line 63.
  5. Session launches with zero DM context. No indication anything went wrong.

Path 2: Outer catch swallows everything else

Lines 163–166:

} catch {
  // If WP-CLI is unavailable or Data Machine isn't installed, silently skip.
  // The plugin is a no-op on systems without Data Machine.
}

Any JS-level error — JSON parse failure, schema mismatch in DM output, OpenCode API change, plugin permission denied — is silently swallowed. The "no-op when DM not installed" intent could be detected explicitly (e.g. which wp first); blanket-catching is too coarse.

Path 3: isUserOverride flags default-only blocks

Line 178:

function isUserOverride(agent: Record<string, unknown>): boolean {
  return typeof agent.model === "string" && agent.model.length > 0;
}

After #60's prompt migration, agent.build blocks contain { mode: "primary", model: "anthropic/claude-opus-4-7" } — same as the top-level model. isUserOverride returns true for these because typeof model === "string", even though the user never customized anything. This means even when #113's agents.length === 1 gate fires, the override check inside it skips the prompt write.

Why it matters

This is the silent-failure substrate that turned a one-line incompleteness in #60 into weeks of degraded context across the fleet. The user only discovered it by asking an agent in a Discord thread "do you have AGENTS.md?" — and even then it took manual log inspection to find that the plugin had been running but doing nothing useful.

Same family as:

This is a recurring pattern; the plugins need a uniform observability story.

Proposed fix

Surface failures to OpenCode logs

Replace .nothrow() with explicit error handling that logs to OpenCode's logger so failures appear in the same stream as other plugin errors:

const composeResult = await $`wp datamachine memory compose --allow-root`.quiet().nothrow();
if (composeResult.exitCode !== 0) {
  console.warn(`[dm-agent-sync] memory compose failed (exit ${composeResult.exitCode}): ${composeResult.stderr}`);
}

const agentsResult = await $`wp datamachine agents list --format=json --allow-root`.quiet().nothrow();
if (agentsResult.exitCode !== 0) {
  console.warn(`[dm-agent-sync] agents list failed (exit ${agentsResult.exitCode}): ${agentsResult.stderr}`);
  return;
}

Tighten the outer catch

Detect "Data Machine not installed" up front instead of catching everything:

const wpAvailable = (await $`command -v wp`.quiet().nothrow()).exitCode === 0;
if (!wpAvailable) return;  // Genuine no-op case.

// ... rest of plugin without an outer try/catch, so real bugs surface.

Fix isUserOverride to detect actual customization

Compare against config defaults instead of just checking key presence:

function isUserOverride(agent: Record<string, unknown>, topLevelModel?: string): boolean {
  // A block is user-customized if it has fields beyond the safe defaults,
  // or if its model differs from the top-level model.
  const safeKeys = new Set(["mode", "model"]);
  const extraKeys = Object.keys(agent).filter((k) => !safeKeys.has(k));
  if (extraKeys.length > 0) return true;  // tools, prompt, description, etc.
  if (agent.mode && agent.mode !== "primary") return true;
  if (agent.model && agent.model !== topLevelModel) return true;
  return false;
}

End-to-end verification at session start

Optional but ideal: after registering, dump a one-line summary to OpenCode logs:

[dm-agent-sync] registered 10 DM agents; build/plan now use slug=extra-chill-bot, prompt=AGENTS.md+4 files

Then a grep on kimaki.log answers "is the plugin doing anything?" without needing to inspect the next session's system prompt.

Test plan

  • WP-CLI exits non-zero → warning appears in OpenCode logs.
  • Data Machine not installed (no wp binary) → silent skip, no warning.
  • wp datamachine agents list returns malformed JSON → warning, no JS exception.
  • agent.build = { mode: "primary", model: "<top-level model>" } (post-fix(opencode): use instructions array, not agent.build.prompt #60 orphan) → isUserOverride returns false → prompt injected.
  • agent.build = { tools: {...}, model: "..." } (genuine user override) → isUserOverride returns true → preserved.
  • Session-start log line confirms how many agents were registered and which prompt is wired into build/plan.

Related

Environment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions