Skip to content

BookmarkSweep ships in the public payload but depends on the private _X skill, so its launchd job fails every 30 minutes forever #1750

Description

@xmasyx

Summary

BookmarkSweep.ts, InstallBookmarkSweep.ts and com.lifeos.bookmarksweep.plist.template ship in the public install payload, but the sweep's only two dependencies live in skills/_X/, an underscore-prefixed private skill that is not in the payload. On a public install the launchd job therefore fails on its very first step, forever, every 30 minutes.

Evidence

Shipped in the payload:

  • LifeOS/install/LIFEOS/TOOLS/BookmarkSweep.ts
  • LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts
  • LifeOS/install/LIFEOS/TOOLS/com.lifeos.bookmarksweep.plist.template

Not shipped — LifeOS/install/skills/ contains 53 skills and no underscore-prefixed ones; skills/_X returns 404.

The hard dependency, BookmarkSweep.ts lines 43-45:

const X_DIR = join(HOME, ".claude", "skills", "_X");
const BOOKMARKS_TOOL = join(X_DIR, "Tools", "bookmarks.ts");
const BOOKMARK_ISSUE_TOOL = join(X_DIR, "Tools", "bookmark-issue.ts");

fetchBookmarks() spawns bun <BOOKMARKS_TOOL> fetch 50 --json. With the file absent, bun exits 1, so the sweep returns { error: "fetch exited 1" } and exits 0 having done nothing.

Observed on one public install: 458 consecutive runs, 0 successes, spanning two system versions and starting the day the job was first bootstrapped. Every log line is byte-identical apart from the timestamp:

{"ts":"...","fetched":0,"new":0,"created":0,"skipped":0,"auditExit":null,"issueUrls":[],"error":"fetch exited 1"}

Why this is a defect and not just an unused feature

DOCUMENTATION/Work/WorkSystem.md already documents the exclusion policy for exactly this class of component:

Private component — NOT in the public release payload. hooks/ULWorkSync.hook.ts and the _ULWORK skill are principal-specific (they target the private UL work repo) and are rsync-excluded from public releases, the same as any underscore-prefixed private skill.

BookmarkSweep.ts is in the same class. Its classifier prompt triages bookmarks into "UL work ideas" and its VALID_PROPERTIES set is newsletter, website, youtube, podcast, community, consulting, open-source, internal — a principal-specific taxonomy. But unlike ULWorkSync.hook.ts, the tool and its installer were not excluded.

The failure is also silent by design: the sweep logs to MEMORY/OBSERVABILITY/bookmarksweep.jsonl and always exits 0, so launchd reports a healthy job and nothing surfaces. A public installer who bootstraps it gets a permanently dead 30-minute timer that looks alive.

Proposed fix

Preferred — exclude the three files from the payload, consistent with _ULWORK and ULWorkSync.hook.ts. Nothing in install.sh references InstallBookmarkSweep, so no install path breaks.

If the tool should keep shipping, make the installer fail closed instead of arming a job that cannot work. In InstallBookmarkSweep.ts, before materializing the plist:

const BOOKMARKS_TOOL = join(HOME, ".claude", "skills", "_X", "Tools", "bookmarks.ts");

async function install(): Promise<void> {
  if (!existsSync(BOOKMARKS_TOOL)) {
    console.error(
      `[InstallBookmarkSweep] refusing to install: ${BOOKMARKS_TOOL} not found.\n` +
      `BookmarkSweep depends on the private _X skill, which is not part of the public payload.`,
    );
    process.exit(1);
  }
  // ... existing install path
}

That turns a permanently silent runtime failure into one loud message at the only moment a human is watching.

A third option, if neither fits: have fetchBookmarks() distinguish "dependency missing" from "fetch failed", so the log line says which of the two happened rather than reporting a generic exit code 458 times in a row.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions