What it does
Kanban Code is a native macOS app (SwiftUI, requires macOS 26 Tahoe) that manages multiple Claude Code sessions as cards on a kanban board. Each card ties together a Claude session, a git worktree, tmux terminals, and a GitHub PR. Cards move between columns — Backlog, In Progress, Waiting, In Review, Done — automatically based on real activity signals from Claude Code hooks.
Why I starred it
Running multiple Claude Code agents at once is the workflow everyone lands on eventually. The problem isn't launching them — it's tracking which agent is doing what, which one is stuck waiting for input, and which PR belongs to which task. Kanban Code solves the context switching problem by making every session a first-class object on a board. Push notifications when agents need attention, remote execution to offload work to a server, session forking and checkpoints — it's the control plane Claude Code doesn't ship with.
How it works
The architecture is the most interesting part. It follows Elm-style unidirectional data flow in Swift, which is unusual for a SwiftUI app. All state lives in a single AppState struct in Sources/KanbanCodeCore/UseCases/BoardStore.swift. Every mutation goes through a Reducer that returns (State', [Effect]) — the new state plus a list of side effects. The EffectHandler actor then executes those effects (disk writes, tmux commands, network calls) asynchronously.
// Sources/KanbanCodeCore/UseCases/EffectHandler.swift
public func execute(_ effect: Effect, dispatch: @MainActor @Sendable (Action) -> Void) async {
switch effect {
case .persistLinks(let links):
try await coordinationStore.writeLinks(links)
case .createTmuxSession(let cardId, let name, let path):
try await tmuxAdapter?.createSession(name: name, path: path, command: nil)
await dispatch(.terminalCreated(cardId: cardId, tmuxName: name))
// ...
}
}
Views never mutate state directly. This makes the entire state machine testable without UI, and the test suite reflects that — 30 test files covering reconciliation, card lifecycle, reducer logic, launch flows, and more.
The reconciler
The heart of the system is CardReconciler in Sources/KanbanCodeCore/UseCases/CardReconciler.swift. It solves what the README calls the "triplication bug" — the same piece of work appearing as three separate cards because a session, a worktree, and a PR all look like independent things.
The reconciler takes a DiscoverySnapshot (all sessions, tmux sessions, worktrees, PRs) and matches them against existing cards using a priority chain:
- Exact
sessionIdmatch - Git branch match (with project path scoping to prevent cross-project collisions)
- Project path + tmux match for pending cards
- Worktree directory name decoding for sessions without metadata yet
After matching, it runs dedup passes — absorbing orphan worktree cards into real ones, merging duplicate session IDs from race conditions, and clearing dead tmux/worktree links when the underlying resource is gone. The reconciler is a pure function (public static func reconcile(existing:snapshot:) -> [Link]), which makes it straightforward to test every edge case without mocking.
Card identity
Cards use KSUIDs (K-Sortable Unique IDs) from Sources/KanbanCodeCore/Infrastructure/KSUID.swift — a 4-byte timestamp plus 16 bytes of randomness, base62-encoded to 27 characters. They sort chronologically without needing a database, and the prefix convention (card_2MtC...) makes debugging logs readable.
Search
Session search uses BM25 scoring (Sources/KanbanCodeCore/UseCases/BM25Scorer.swift) with prefix matching for terms 3+ characters and a linear recency boost (3x for today, decaying to 1x over 30 days). It indexes the raw .jsonl conversation files from ~/.claude/projects/. The b parameter is set to 0.4 (vs the standard 0.75), which dampens length normalization — makes sense given that longer sessions aren't inherently less relevant.
// BM25Scorer.swift — recency boost
public static func recencyBoost(modifiedTime: Date) -> Double {
let daysAgo = Date.now.timeIntervalSince(modifiedTime) / 86400
if daysAgo <= 0 { return 3.0 }
if daysAgo >= 30 { return 1.0 }
return 3.0 - (2.0 * daysAgo / 30.0)
}
Using it
Grab the .app from GitHub Releases, drop it in Applications. It auto-discovers existing sessions from ~/.claude/projects/. Add a project, configure Claude Code hooks (the app offers to install them on first launch), and start tasks from the backlog.
The real power shows up when you're running 5+ agents. Each card shows its tmux terminal inline (powered by SwiftTerm), PR status with CI checks, and the current activity state. Hit Cmd+K to search across every session you've ever run.
Rough edges
macOS 26 only — no backwards compatibility. The app isn't notarized, so the first-launch dance (right-click, Open, System Settings) is annoying. Windows support exists via a Tauri port in windows/, but it's clearly secondary.
Dependencies are minimal for the core (KanbanCodeCore has zero external deps), but the app layer pulls in SwiftTerm and swift-markdown-ui. Remote execution requires Mutagen, push notifications need a Pushover account. It's a lot of optional setup.
The project is actively maintained — five commits on April 6th alone — but it's still early. At 151 stars, the community is small. Documentation lives mostly in specs/ files and the README.
Bottom line
If you're running multiple Claude Code agents daily and losing track of which session is doing what, Kanban Code is the best answer I've seen. The Elm-style architecture and pure-function reconciler show real engineering care — this isn't a weekend hack wrapped in a GUI.
