Skip to main content

Code Forge

Connect Daintree to GitHub and other forges: token setup, issues, pull requests, repo stats, CI status, and per-project provider routing.

Updated
Reviewed

Forge providers

Daintree used to integrate only with GitHub. That integration is now a general forge provider model. A forge is the platform layer that sits on top of git. It handles issues, pull requests, reviews, CI status, releases, and authentication.

GitHub is the built-in default provider, and the only one that ships today. Other forges (GitLab, Bitbucket, Gitea, and self-hosted servers) can be added through the plugin system. Everything on this page describes the GitHub provider, but the structure underneath is the same for any forge.

In practice, Daintree works with GitHub out of the box with no extra setup. To connect a different forge, you install a plugin that registers it as a provider. See the plugin system for Daintree's extension model.

How Daintree chooses a provider

When you open a project, Daintree picks a forge provider by checking three things in order:

  1. A per-project override. If you've pinned this project to a specific provider, that wins.
  2. Your global default. If you've set a default provider in settings and it covers the project's remote, Daintree uses it.
  3. Hostname auto-detection. Otherwise Daintree reads the git remote URL and matches its hostname against the installed providers. A github.com remote resolves to the built-in GitHub provider.

An explicit choice wins; detection is the fallback. For most people none of this needs touching. A GitHub repository is detected on its own, and the override and default only matter once you have more than one forge in play.

Note
The override and the global default behave differently when they don't match a project. A per-project override always applies, even to a remote it doesn't obviously fit. A global default applies only when it actually covers the project's remote. If it doesn't, Daintree falls through to hostname detection.

Provider settings

Forge configuration lives in Settings > Code Forge. A selector at the top of the tab switches between sections: General, the GitHub provider, and any other provider you've installed. Each installed provider gets its own section, with a credential form built from the fields that provider declares.

General

The General section holds the global default and a view of how the current project resolves.

Default forge provider sets the provider used for newly opened projects. The default option is No global default (auto-detect from hostname), which leaves detection in charge. Each installed provider appears below it, showing the hostnames it matches. A per-project override still wins over this. The default only applies when no override is set and it covers the project's remote.

Active project routing lists the current project's git remotes and shows, for each one, which provider it resolves to and why. Each row carries a badge:

BadgeMeaning
OverrideResolved from this project's provider override.
DefaultResolved by your global default provider.
HostnameResolved by matching the remote's hostname.
No matchNo provider matches this remote. Install a plugin, or set a global default that covers the hostname.

If you're ever unsure why a project is or isn't talking to a forge, this table is the place to check.

Per-project override

To override forge behavior for a single project, open Project Settings and go to the Code Forge tab. Two settings live here.

Forge remote selects which git remote Daintree uses for issues, PRs, and pulse data. The default, Auto-detect (origin), keeps the standard behavior. The other options list each configured remote with its name and owner/repo label. Change this when origin points to a fork and you'd rather track the upstream repository.

Forge provider pins the project to a specific provider. The default, Auto-detect, leaves provider selection to the global default and hostname detection. Set it explicitly when detection picks the wrong provider, or when a remote's hostname doesn't make the right provider obvious.

GitHub provider

The GitHub provider is the one that ships with Daintree. Connecting it takes a single personal access token.

Personal access token

Open Settings > Code Forge, select GitHub, and find the Personal access token block. The token is used for repository statistics, issue and PR detection, and linking worktrees to GitHub. It also means you don't need the gh CLI configured for any of these features.

Paste a token into the field. The placeholder shows the two accepted formats, ghp_... or github_pat_.... Use the buttons below it:

  • Test validates the token against GitHub without saving it. A valid token reports back, so you can confirm it works before storing it.
  • Save stores the token. The sidebar and toolbar refresh as soon as it's accepted, with no restart needed.
  • Clear removes a stored token. It only appears when one is saved.

The token is stored locally in Daintree's config file, as plain text, with owner-only file permissions on macOS and Linux. It is never sent to Daintree. A green GitHub connected indicator confirms a working token. See Security & Privacy for the full storage model.

Note
A GitHub token covers repo stats, issue and PR detection, and issue linking. You don't need gh configured for any of it.

Required scopes

A second block, Create a new token, has a Create Token on GitHub button that opens GitHub's token page with the right scopes prefilled. A classic personal access token needs:

  • repo: access repository data (issues, PRs, statuses, and private repositories)
  • read:org: read organization membership, required for private repos owned by an org
Tip
If you'd rather use a fine-grained token, the scopes map differently. Grant Contents: Read and write and Metadata: Read, and use Members: Read in place of read:org. Fine-grained tokens also cap out at a one-year lifespan, so they need renewing on a schedule.

Repo stats toolbar

With the GitHub provider connected, the toolbar shows live counts for the active project:

  • Open issues on the repository
  • Open pull requests
  • Unpushed commits on the current branch

The counts update on their own. Click any one to open its dropdown panel. On a narrow toolbar the stats move into the ··· overflow menu instead.

Freshness

Each count carries a small indicator of how current it is, so you know whether you're looking at fresh data or a cached snapshot:

  • Fresh (under ~90 seconds old): no extra marking.
  • Aging (a few minutes old): a clock glyph and a note like · updated 3m ago.
  • Cached from a previous session: shown right after launch, before the first live refresh lands.
  • Couldn't reach GitHub: the last known counts stay on screen with a note that the refresh failed.

Hovering a stat warms its dropdown in the background after a brief delay, so the panel is ready by the time you click. If the cached data is recent enough, Daintree skips the prefetch.

Activity chip

When a count goes up while its dropdown is closed, Daintree flags the new activity. A small chip appears in the corner of the pill, and the count animates as it changes. It's a glance-able cue that something arrived, not a literal "+N" badge and not an unread counter. The chip clears on its own after a few minutes, or as soon as you open that dropdown.

Rate limits

If GitHub starts rate-limiting requests, a clock glyph appears on the affected pill. Hover it for a per-bucket breakdown: how many requests remain in each rate-limit bucket, each with a live countdown to its reset. Reading the breakdown doesn't itself cost any quota.

Polling adjusts to whether Daintree is in front of you. It refreshes roughly every 30 seconds while the app is focused, and backs off to about every 5 minutes when it's in the background. Waking your machine from sleep triggers an immediate refresh.

Issues and PR panel

Click the issue or PR count in the toolbar to open the dropdown panel. It opens with the search input focused, so you can start typing right away.

State filter tabs at the top narrow the list. Issues show Open and Closed. PRs show Open, Merged, and Closed. The panel also takes number-based search patterns for quick lookups:

  • #42 fetches a single issue or PR by number
  • 42,45 fetches multiple specific items
  • 42-50 fetches a range
  • 42+ fetches an open-ended range from that number onwards

At the bottom, View on GitHub opens the filtered list on GitHub.com, and New opens GitHub to create an issue or PR.

Instant open and caching

The panel opens without a spinner on a cold start. The first time you open it after launching the app, it shows results restored from a disk cache written by the previous session. Within a session, reopening the panel shows recently cached results from memory while a background refresh runs quietly.

The in-session cache is keyed by project, resource type, filter state, and sort order, so switching any of those triggers a fresh fetch. Search results always fetch live and aren't cached.

Note
Only that first open after launch is served from disk. The in-session cache is in memory and short-lived (about 45 seconds), which is why reopening a panel later in the same session still kicks off a quiet background refresh.

Persistent panel state

The panel keeps its state between opens within a session. Your scroll position, search text, and filters are still there when you reopen it, rather than resetting every time. The first open after launch renders the real list without a skeleton flash, because the panel is primed as the toolbar mounts.

Keyboard navigation

The panel can be driven entirely from the keyboard:

  • / moves through the list from the search input
  • Enter creates a worktree for the focused item (if it's open and has no worktree yet) or switches to it (if a worktree already exists)
  • Cmd+Enter (or Ctrl+Enter) opens the focused item on GitHub
  • Escape clears the current selection first, then closes the panel on a second press

Sort order

A filter icon next to the search input opens a sort popover with two options:

  • Newest (default): sorts by creation date, matching GitHub.com's default order
  • Recently updated: sorts by last activity

When Recently updated is active, a small accent dot appears on the filter icon, so you can tell at a glance. The choice applies to both the browse list and typed search results, and it's tracked separately for issues and PRs.

Note
Sort order is session-only. It resets to "Newest" when you restart Daintree.

Row details

Each row uses a two-line layout that keeps the key information visible at a glance:

  • Top row: state icon, title, CI status indicator (open PRs only), and the #number badge (click it to copy the number to your clipboard)
  • Bottom row: author, time since last update, branch name (PRs) or labels (issues, up to two shown with color dots), comment count, linked PR icon (issues with an associated PR), assignee avatar, worktree indicator, and a ··· context menu

The ··· context menu offers Open in GitHub, Switch to Worktree (if one exists), and Create Worktree (if the item is open and has no worktree yet). Fork PRs show the create option disabled with a tooltip explaining why.

CI status icons

Open PR rows show a CI status indicator between the title and the PR number:

  • Green check: all checks passed
  • Amber dot: checks pending
  • Red X: checks failing

Hover the icon for a tooltip describing the status. If the PR has no CI checks configured, no indicator appears.

When GitHub is unreachable

The panel degrades gracefully rather than going blank. When a refresh fails or requests are paused, it keeps showing the last results it fetched, with an inline banner that says how stale the data is:

  • Rate-limited: a banner notes that requests are paused, shows when they'll resume, and stamps how long ago the data was last updated.
  • Network error: a banner reads Couldn't reach GitHub. Showing last known results. with a Retry button. If the failure is a token problem, the button opens settings instead.

Whenever you're not actively searching, a footer line shows when the list was last updated.

Creating worktrees from issues

The quickest way to start on a GitHub issue is to create a worktree straight from it:

  1. Click the issues count in the toolbar to open the issues panel
  2. Find or search for the issue you want to work on
  3. Click Create Worktree on the issue row
  4. The create worktree dialog opens with the issue pre-linked

What gets auto-populated

Creating a worktree from an issue fills in:

  • Branch name: generated from the issue title and number (e.g. feature/issue-42-add-dark-mode). The prefix is inferred from issue labels and title keywords (bugfix/ for bugs, docs/ for documentation, and so on).
  • Worktree path: filled in from your configured path pattern
  • Base branch: defaults to main or master

Every field is editable before you create. If a branch name already exists, Daintree auto-increments it (e.g. feature/issue-42-add-dark-mode-1).

Assign to self

When your GitHub user is authenticated, the dialog shows an Assign to me toggle. Turn it on and the issue is assigned to you on GitHub as the worktree is created. The preference is remembered across sessions.

Run a recipe on create

If you have recipes configured, you can pick one to run when the worktree is created. It launches your preferred agents and terminals right away.

Tip
Use the {{number}} variable in recipe prompts to insert the issue or PR number at launch time. One recipe then works for both issue-based and PR-based worktree creation.

Creating a worktree from a PR

You can create a worktree directly from a pull request, too. Each open PR row has a Create Worktree action, on hover or via the ··· menu.

The dialog opens in a checkout mode tailored for PRs. It shows a PR info banner and pre-fills the branch name from the PR's head branch. When that remote branch has been fetched locally, Daintree uses it as the checkout target. When it hasn't, Daintree fetches it. Only if that fetch fails does it show a warning and disable the create button, so you don't accidentally branch off main instead.

Note
Fork PRs show the Create Worktree option disabled with a tooltip. Fork branches live in a different remote, so the standard checkout flow doesn't apply to them.

Bulk worktree creation

When you need worktrees for several issues or PRs at once, the bulk flow saves you from doing them one at a time.

Selecting items

Hover the state icon on any row and it turns into a checkbox. Click to select that item. Once one is selected, every row switches to checkboxes so you can keep going. Use Shift+click to select a range between the last selected item and the one you click. When a search is active, Select all and Select unassigned (issues only) shortcut buttons appear in the panel header.

Closing and reopening the panel within the same project keeps your selection intact, so a quick glance elsewhere doesn't lose it. Switching projects clears the selection, and so does dismissing the bulk-create dialog, whether you finish, cancel, or press Escape.

The bulk action bar

As soon as you select one or more items, a floating bar slides up from the bottom of the panel. It shows the number selected, a Create Worktrees button, and an X to clear the selection. The X stays responsive even while the bar is animating out.

Reviewing before creating

Clicking Create Worktrees opens a dialog listing every selected item with its planned branch name. Items that can't be created are dimmed with a badge explaining why:

  • Closed or Merged: the issue or PR is no longer open
  • Has worktree: a worktree already exists for this item
  • No branch info: the PR is missing a head branch reference

The dialog also has an Assign to me toggle (issues only) and an optional recipe picker if you want agents to launch in each new worktree.

Creation progress

Once you confirm, Daintree creates the worktrees three at a time. Each item shows its own state: a spinner while in progress, a checkmark when done, or a warning icon if something went wrong. A progress bar and summary track the overall count. The concurrency limit sits at three because git worktree add --no-track avoids the .git/config.lock contention that used to block parallel operations.

Transient errors (git lock files, rate limits, connection blips) are retried up to twice with backoff, which clears most of the common hiccups without any intervention.

When creation finishes

When the batch completes, the dialog shows a summary. If any items failed, a Retry Failed button re-queues only those, and leaves already-created worktrees alone. Done closes the dialog and switches to the last worktree created successfully. A system notification confirms the result and how many worktrees were created.

Automatic issue detection

Daintree parses branch names to detect linked GitHub issues. Recognized patterns include:

  • feature/issue-42-fix-header
  • bugfix/gh-15-login-bug
  • issues/123

When an issue is detected, the worktree card shows an issue badge with the number and title, and tracks the issue's open and closed state.

Manual issue linking

If a worktree already exists and you want to link it to an issue after the fact, you can attach one by hand. This covers the case where you started work before an issue was filed, or where branch-name detection didn't match the right issue.

  1. Right-click a worktree card, or click the ... button in the card header, to open the context menu
  2. Select Attach to Issue… (or Change Issue… if an issue is already linked)
  3. The Attach Issue dialog opens with a search field and filter tabs
  4. Search by title or issue number, and use the Open, Closed, or All tabs to filter by state
  5. Select an issue from the list to attach it

The Attach Issue dialog

The dialog gives you a searchable, filterable list of issues from the repository. A few details worth knowing:

  • The search field filters by both title and issue number, with a short debounce so results update as you type
  • State filter tabs (Open, Closed, All) let you attach closed issues too. The filter resets to Open each time the dialog opens
  • Keyboard navigation uses and to move through the list, and Enter to attach the selected issue
  • An already-attached issue appears in the list with an attached badge, so the current link is visible at a glance

Detaching an issue

To remove a linked issue, open the same dialog through Change Issue…. A Detach Issue button appears in the footer when an issue is currently attached. Click it to remove the association entirely.

Note
Manual attachments take precedence over automatically detected issues from branch-name patterns. If you detach a manually linked issue and the branch name still matches a detection pattern, that auto-detected issue reappears on the card.

Effect on the worktree card

When any issue is linked to a worktree, whether attached by hand or detected automatically from the branch name, the worktree card updates to reflect it:

  • The issue title becomes the card's primary headline, replacing the branch name at the top
  • The branch name moves to a secondary row below the headline
  • Clicking the issue title opens the issue on GitHub in your browser
  • Hovering the issue title shows a tooltip with full details: title, description, labels, and state

Persistence

Issue associations are stored in Daintree's local settings, not in your git repository. They survive app restarts, and they're cleaned up when you delete the worktree. Each worktree can have one attached issue at a time.

Automatic PR detection

When you switch to a worktree, Daintree checks whether its branch has an open pull request. The root worktree is always excluded from branch-name matching, so it won't pick up a spurious PR badge when its branch happens to match a PR's head. Only feature worktrees get auto-associated this way. When a PR exists:

  • The worktree card shows a PR badge with the number and state (open, draft, merged, or closed). When a remote is connected but no PR exists, the badge reads "No PR".
  • PR status is polled periodically to keep the badge current
  • Click the badge to open the PR in the Portal

Commit-author avatars

Commit-author avatars on worktree cards resolve through whichever forge provider is active for that project, rather than being hardcoded to GitHub. Daintree picks an avatar in this order:

  1. A branded icon when the author is an AI agent (Claude, Codex, Gemini)
  2. The author's profile picture from the active forge provider
  3. A Gravatar match for the commit email
  4. Deterministic colored initials when nothing else resolves

Today only the GitHub provider resolves a forge profile picture. Other forges fall through to Gravatar until those providers ship that capability.

Hover tooltips

Hover a PR or issue badge on a worktree card to see more: title, description summary, labels, and current state, pulled straight from the forge.

Token resilience and rate limits

When the connection to GitHub fails, the integration degrades gracefully rather than breaking.

When your token expires or is revoked, a banner appears reading GitHub token expired with the line Reconnect to restore issue, PR, and repository data and a Reconnect to GitHub button. The button takes you to Settings > Code Forge > GitHub to paste a fresh token. This is the main reason the reconnect banner exists. Personal access tokens expire, and Daintree surfaces that clearly instead of silently failing to fetch.

Inside the issues and PR panel, a token error shows its own inline banner with a Settings button (not "Reconnect") that opens the same place. The panel keeps showing the last results it fetched in the meantime.

For transient network errors, Daintree retries quietly in the background with exponential backoff, up to three attempts. Token and rate-limit errors aren't retried this way, since retrying wouldn't help.

Third-party providers

GitHub is the only forge provider that ships with Daintree today. Support for other forges (GitLab, Bitbucket, Gitea, self-hosted servers) arrives as plugins that register a provider. Daintree handles resolution, settings, and the credential form the same way it does for GitHub.

Those providers install through the plugin system, Daintree's extension model.