Skip to content

Dreamer never runs on the OMP harness: an unguarded harness throw aborts the whole timer tick #496

Description

@Qiiks

The dreamer never runs on the OMP harness: an unguarded harness throw aborts the whole timer tick

On the OMP harness the dreamer is completely dead. Not slow, not failing per task — nothing dispatches, and has not since 2026-09-17. The cause is one unguarded throw in the message-history orphan sweep that aborts the timer tick body before the per-project loop is ever reached.

Evidence

magic-context.log, every tick since 2026-09-17 21:03:

[dreamer] timer tick (startup) — projects=1
[magic-context] timer-triggered maintenance check failed: OpenCode orphan sweep cannot read a omp host store
Error: OpenCode orphan sweep cannot read a omp host store
    at openCodeSweepHarness (…/dist/index-zhevhnk5.js:31684:13)
    at sweepOrphanedOpenCodeMessageIndexes (…:31712:19)
    at runMessageHistoryMaintenance (…/dist/index.js:12586:17)
    at runTick (…/dist/index.js:12562:77)

714 occurrences across the current log and its predecessor. The last dream_runs row on this store is 2026-09-17 01:08; it is now 2026-09-21. 605 of 637 task_schedule_state rows are next_due_at <= now and nothing is picking them up.

The defect

runTick (plugin/src/plugin/dream-timer.ts:297) does message-history maintenance before per-project work, in one try:

const db = openTimerDatabaseOrNull("maintenance tick");
if (!db) return;
await runMessageHistoryMaintenance(db);        // <-- throws here
for (const reg of registeredProjects.values()) {
    ...
    await runProjectMaintenance(reg, origin, db);   // <-- never reached
}

and runMessageHistoryMaintenance calls sweepOrphanedOpenCodeMessageIndexes, whose first act is:

// message-index.ts:813
const harness = openCodeSweepHarness();   // throws for any non-OpenCode harness
// message-index.ts:743
function openCodeSweepHarness(): "opencode" | "opencode2" {
    const harness = getHarness();
    if (harness === "opencode" || harness === "opencode2") return harness;
    throw new Error(`OpenCode orphan sweep cannot read a ${harness} host store`);
}

The catch is in runTick, so the throw is contained — it logs and the tick ends. That is what makes it invisible: no crash, no failed task, no error state on any task row. Just a tick that quietly returns early, forever.

The function already has the right pattern for exactly this situation twelve lines later. The readable-db path is guarded:

// message-index.ts:820
let openCodeDb: Database | null = null;
try {
    openCodeDb = openReadableOpenCodeDb();
} catch {
    openCodeDb = null;
}
if (!openCodeDb) {
    // Mirror the git sweep's non-indexable parking: future-date the last
    // sweep so the normal cooldown arithmetic re-probes after one day.
    persistMessageHistoryOrphanSweepState(...);

A harness that cannot be swept is the same class as a DB that cannot be read — it should park and re-probe, not throw. The guard was written for one and not the other.

Suggested fix

Move the harness check inside the existing non-indexable path, so it parks like the unreadable DB does:

const harness = harnessSupportsOpenCodeOrphanSweep() ? getHarness() : null;
if (!harness) {
    persistMessageHistoryOrphanSweepState(db, /* future-dated */);
    return { status: "unavailable", scanned: 0, deleted: 0, cursor: "" };
}

Two things I would suggest beyond the minimal fix:

  1. The ordering is the real hazard, not the throw. Any throw from runMessageHistoryMaintenance kills the entire dreamer for that tick, on every harness. Even with this guard fixed, the next such throw silently disables scheduling again. Wrapping the maintenance call in its own try — or moving it after the per-project loop — would make the failure mode local instead of total.
  2. The symptom is unobservable from outside. The failure is a log line and nothing else; the task rows stay None/skipped and /ctx-status shows nothing wrong. Whatever the fix, a tick that aborts before per-project work is worth surfacing — it is the difference between "the dreamer has nothing to do" and "the dreamer is not running", which is exactly the distinction your Optional decision-provider for memory importance classification (cache-neutral, default off) #474 reply drew for the classify task.

Environment

OMP harness (harness === "omp"), plugin @cortexkit/pi-magic-context 0.42.5, Windows. Message-history orphan sweep is OpenCode-specific by design — the issue is only that its precondition is asserted with a throw on a harness the plugin otherwise fully supports.

Reproducer

Any OMP (or Pi) host with the plugin loaded and the dreamer enabled will do it — it fires on the first tick, and every tick after. magic-context.log under %TEMP%\<harness>\magic-context\ is where the line lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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