Task Hub — Codex-style tasks, inside CodeNow.
Beta users were running CodeNow next to Codex because Codex had something CodeNow didn't: a single cross-project list of every task you've ever asked an agent to do, with its own git branch, its own status, its own follow-up thread. So they'd code in CodeNow and tab-switch to Codex to see "what was I working on?" That's the gap v0.1.32 closes.
What you'll see
A new tab on the left sidebar — sixth one, between Agents and the integrations rail. Open it and you get a cross-project list of every task you've ever started, grouped by project, with a status pill, a branch chip, and a relative timestamp. Click any row, and the editor area becomes a three-pane task detail view. ⌘⇧N anywhere creates a new one.
The branch chip is real. Every task gets its own git branch by default — codenow/tsk-<id> off whatever base you picked at creation time. Your working directory is untouched; the branch sits there waiting until you check it out from the terminal to do the actual work. If you toggle the "Create isolated branch" checkbox off in the new-task modal, the task lives on the base branch instead.
Why branch-per-task is the default
Three reasons we copied this from Codex:
- Clean rollback. If the agent makes a mess,
git branch -D codenow/tsk-<id>erases it. Nothing on your main branch. - Parallel work. Three tasks, three branches. Switch between them with
git checkout. No interleaving, no "wait did I commit the OAuth change?" - Review is a real action. When the task is done, you merge it back to base. That's a deliberate decision, not a side effect of typing.
If the project isn't a git repo, the branch toggle disables itself and shows an honest warning. If git refuses the branch op (dirty index, missing base), the task record still saves and you see the error inline. No silent failures.
The task detail view
Header at the top: title (editable in v0.1.33), branch chip, base-branch arrow, status pill, model badge, permission-mode pill, relative time, project name. Conversation pane on the left shows the initial prompt you wrote at task creation and (after v0.1.33) the recorded turns from the JSONL replay system that already underpins the Runs tab. Files-changed pane on the right will surface per-file diffs in v0.1.33.
The five status pills
| Status | Pill | What it means |
|---|---|---|
| running | running | Agent is still working |
| needs-review | needs review | Default after creation; agent finished, awaiting your call |
| merged | merged | Branch merged into base |
| discarded | discarded | Branch deleted without merge |
| failed | failed | Agent errored out |
[Update ↑] and [Discard] buttons are visible but disabled in v0.1.32 — their tooltips say "ships in v0.1.33." That's the no-mocks rule applied to itself: no destructive git operations until the smoke matrix has had a release to settle without regressions. v0.1.33 wires both to real git merge / git branch -D.
Cross-project, by design
The task list is one global file (~/.codenow/tasks.json), so the sidebar can show every task across every project you've opened. The chip row at the top — All · 3 · codenow · 3 · xspan-api · 12 — lets you scope to one project or see everything. Sticky group headers keep you oriented when scrolling.
This is the part that makes the difference vs. "oh just check the Runs tab" — Runs is per-project. If you're switching between four repos like Karl does, you want one screen that says here's everything in flight, regardless of which window.
Migration from existing runs
First time you open the Tasks tab after upgrading, CodeNow scans every project's .codenow/runs/ directory and creates a task entry for each recorded run that doesn't already have one. They show up grouped under their project, default to status merged (best guess for "this is historical"), and are tagged backfilled: true internally. The migration is idempotent — re-runs are no-ops.
What also shipped — pair-programming fix
If you hit "Live edit failed: crdt timeout (renderer crdt.js not loaded?)" when starting a pair-programming session in v0.1.31, that's gone. Real cause: Supabase Realtime channel subscribe was awaited with no timeout and no error handling, so any non-SUBSCRIBED status (most commonly CHANNEL_ERROR from an expired JWT) hung the promise. The preload's 8-second safety net then fired with the misleading "renderer not loaded?" message.
Now: channel.subscribe handles every status, an inner 10-second timeout fires with the actual reason, and the preload safety was raised to 15 seconds. Users see toasts like "Live edit failed: Supabase Realtime subscribe failed (CHANNEL_ERROR): JWT expired" — actionable instead of mysterious. Full forensic walk-through in the v0.1.32 release notes.
And — full regression green
First release in months where the whole test suite is all-green end-to-end:
- Smoke static: 0 errors
- Smoke runtime: 147 / 147 pass
- Playwright E2E: 84 / 84 pass (was 71 / 84 before this release — fixed 13 long-standing stale failures in the integrations-rail suite)
Every Task Hub state was also captured as a screenshot and visually inspected for errors, per Karl's no-mocks rule — empty state, new-task modal, list with tasks, detail view, back-to-code, search filter. No theater.
Get v0.1.32
Auto-updates from any prior version. Mac (Apple Silicon + Intel), Windows, Linux — all signed and notarized.