Skip to content
Second Brain Chronicles
Go back

Tasks Live in Two Places. Neither Knew About the Other.

Tasks Live in Two Places. Neither Knew About the Other.

Tasks live in the vault. Tasks also live on the phone. Neither system knows about the other. Add a task on the phone while walking, it never reaches the vault. Complete a task in the vault, the phone still shows it overdue.

The Bridge

Built a sync script — about 290 lines — that runs in three phases:

  1. Vault → Reminders: Scan vault task files, push incomplete tasks to Apple Reminders
  2. Reminders → Vault: Pull phone-captured tasks back into the vault’s daily note
  3. Save state: Write the mapping file so the next run knows what’s already synced

The mapping uses a composite key: title::filepath. The same task text can appear in multiple vault files — “Review PR” in three different project folders is three different tasks, not one task mentioned three times.

The Unicode Problem

The title extraction broke on tasks containing emoji. The script used sed to strip checkbox syntax and extract the task title. macOS sed can’t handle multi-byte Unicode characters in character classes.

A task like 📱 Check phone captures would silently produce garbage — truncated titles, missing characters, or empty strings. The fix was replacing sed with perl and explicit Unicode codepoint handling.

ToolUnicode Support
macOS sedBreaks on multi-byte characters in character classes
perl with explicit codepointsHandles everything

One line replacement. The kind of bug that’s invisible until you have tasks with emoji in them — which, on a phone, is most of them.

The Dedup Problem

The sync needed deduplication on both directions. Reset the mapping file — which happens during debugging, or when the state file gets corrupted — and the next run would create duplicates everywhere: the same task twice in Reminders, the same captured item imported twice into the daily note.

Fix: before pushing a vault task, check if Reminders already has one with that title. Before importing a phone capture, check if the daily note already contains it.

Cleaned 23 duplicate groups from Apple Reminders during the initial sync — artifacts from earlier sync attempts that didn’t have dedup.

Why Not a Background Process

The obvious question: why not run this as a launchd agent, automatically, every hour?

macOS doesn’t grant persistent Reminders access to background processes. A launchd agent would need to re-request permission on every run, or the permission would silently lapse. The sync engine runs inside the AI assistant session instead — interactive terminal sessions hold Reminders access; background processes don’t.

The accountability hooks

The sync script was half the build. The other half was accountability — hooks that fire during sessions to surface overdue tasks.

A session-start hook checks for overdue and due-today tasks, displaying them before any work begins. A mid-session hook fires every 15 prompts with escalating tone. The escalation exists because the first nudge is easy to dismiss. The third one isn’t.

The Damage Report

MetricValue
Session duration~60 minutes
Script size~290 lines
Sync directions2 (vault→reminders, reminders→vault)
Duplicate groups cleaned23
Unicode fixsed → perl (one line)
Permissions workaroundSession-scoped instead of background process
Accountability hooks2 (session start + mid-session nudge)

Tasks captured on the phone now reach the vault. Tasks completed in the vault update on the phone. The bridge exists. The hardest part wasn’t the sync logic — it was macOS sed refusing to read emoji.


Share this post on:

Previous Post
Vault Reorganization Broke Every Search Index
Next Post
Welcome to Second Brain Chronicles