Skip to main content

Projects

Opening projects, cloning repos, configuring worktree behavior, and tracking health with Project Pulse.

Updated
Reviewed

Projects

A project in Canopy is a Git repository. Every directory you open needs to be a Git repo — if it isn't, Canopy will walk you through initializing one.

Internally, projects are identified by a SHA-256 hash of the git root path. All project data (settings, recipes) is stored per-project in Canopy's app data directory. When in-repository settings are enabled, a shareable subset of that data is also written to a .canopy/ directory in the repo itself.

Opening a Project

You can open a project a few ways:

  • File > Open Directory (Cmd+O / Ctrl+O) to pick a folder from your filesystem
  • The project switcher in the toolbar (see below)
  • File > Open Recent for previously opened projects
  • Clone Repository from the project switcher or File menu to clone a Git repository directly

If the directory you open isn't a Git repo, Canopy walks you through initializing one.

The Project Switcher

The project switcher is how you navigate between projects in Canopy. Click the project name button in the toolbar to open the full dropdown, or use the keyboard shortcut for a quick-switch modal.

Note
The keyboard shortcut (Cmd+Alt+P on macOS, Ctrl+Alt+P on Windows/Linux) opens a focused variant that shows only your currently open projects. It has no temporal sections and no management actions. Press Enter to switch immediately.

Temporal sections

The sidebar dropdown organizes projects into time-based sections so your most relevant work stays near the top. The currently active project always appears first with a subtle background tint. Below that, you'll see:

  • Pinned — projects you've pinned via right-click, always visible regardless of when they were last opened
  • Today — projects opened today
  • This Week — projects opened earlier this week
  • Older — everything else

Empty sections are skipped entirely. When you type in the search box, temporal sections collapse into a flat ranked list.

Project rows

Each project in the list shows four elements: a status dot, a project icon (your chosen emoji), the project name, and secondary text. The secondary text shows context about the project's current state:

  • "Agent working…" (accent color) when agents are actively running
  • "Needs review" (amber) when an agent is waiting for your input
  • A relative timestamp (e.g., "2 hours ago") showing when you last opened the project
  • The directory name as a fallback

Status dots

The colored dot next to each project gives you a quick read on what's happening:

StatusDescription
Pulsing greenAgents are actively working in this project.
AmberAn agent is waiting for your review or input.
Solid greenBackground processes are running, but no agent action needed.
Hollow circleIdle. No running processes.

The sidebar trigger button itself also shows a badge dot when background projects need attention: pulsing green for active agents, amber for agents waiting on review.

Managing projects

Right-click any project row to open the context menu:

  • Open in new window — launches the project in a separate Canopy window
  • Pin / Unpin project — keeps the project in the Pinned section at the top
  • Copy path — copies the project's directory path to your clipboard
  • Stop all agents — terminates all running agent processes (only shown when agents are active)
  • Remove project — removes it from the list (does not delete files from disk). For the active project, this becomes Close project, which closes it and returns you to the Welcome Screen.

If a project's directory is missing from disk, the row shows "Directory not found" in amber and the context menu offers Locate folder to point Canopy at the new location.

Keyboard hints

The footer of the dropdown shows modifier-reactive hints that update as you hold keys:

  • Switch to the selected project
  • + Open in a new window
  • + Remove from list

Sorting order

Projects are sorted by how often and how recently you've opened them. Canopy uses a frecency algorithm (frequency combined with recency) where the score decays over time with a 5-day half-life. New projects start with a boost so they don't immediately sink below older ones.

The active project is intentionally placed last in the quick-switch modal. This means pressing Cmd+Alt+P followed by Enter always takes you to the project you used most recently before the current one, making two-project toggling fast.

Action buttons

At the bottom of the dropdown palette, four action buttons give you quick access to:

  • Project Settings — opens settings for the current project
  • Add Project — opens a directory picker to add an existing folder
  • Clone Repository — opens the clone dialog (see below)
  • Create New Folder — creates a new project directory

Welcome Screen

When no project is currently open, Canopy shows a Welcome Screen instead of the usual workspace. If you've used Canopy before, you'll see your recent projects listed (sorted by how frequently and recently you've opened them), making it quick to jump back in.

New users see a Getting Started checklist that walks through the basics: opening a project, asking an AI agent for help, and starting a parallel task with worktrees. The checklist can be dismissed once you're comfortable.

Quick action buttons on the Welcome Screen let you Open Folder, Create Project, Clone Repository, or Launch Agent without needing to navigate through menus.

Cloning a Repository

If you don't have a repository on your local machine yet, you can clone one directly from within Canopy. This runs git clone under the hood and adds the cloned repo as a project automatically.

There are four ways to open the clone dialog:

  • File > Clone Repository...
  • The project switcher in the toolbar (or the global project switcher modal) has a Clone Repository... button at the bottom of the palette
  • The Welcome Screen shows a Clone Repository button when no project is open
  • The Action Palette includes a "Clone Repository" action

The clone dialog has four fields:

  • Repository URL — accepts HTTPS URLs (https://github.com/user/repo.git), SSH URLs (git@github.com:user/repo.git), or GitHub shorthand (owner/repo)
  • Parent Directory — click Browse to choose where the cloned folder should go
  • Folder Name — auto-derived from the URL (strips the .git suffix), but you can change it
  • Shallow Clone — check this to pass --depth 1, which is useful for large repositories where you don't need the full history
Tip
The owner/repo shorthand is a quick way to clone GitHub repositories. Canopy expands it to https://github.com/owner/repo automatically. For repositories hosted elsewhere (GitLab, Bitbucket, self-hosted), use the full HTTPS or SSH URL.

Once you click Clone, a progress log streams the output in real time. You can cancel at any point during the clone, and Canopy will clean up any partially cloned files. On success, the dialog closes automatically, the project is added to Canopy, and Canopy switches to it.

Canopy uses your system's Git credentials for authentication. HTTPS clones rely on your credential helper (macOS Keychain, Windows Credential Manager, etc.) and SSH clones use your system SSH agent and keys from ~/.ssh. If authentication fails, the clone stops immediately rather than waiting for input. For help setting up SSH keys, see GitHub's SSH documentation.

Project Status

Each project has one of three states:

StatusDescription
ActiveThe currently visible project in the foreground.
BackgroundOpen with running processes, but not displayed. Handy when you want to switch projects without killing terminals.
ClosedNo running processes. Fully dormant.

When closing a project, Canopy asks whether to kill terminals (fully close) or keep them running in the background.

Per-Project Settings

Each project stores its own configuration in a settings.json file, including:

  • CopyTree settings — include/exclude patterns, format, and limits
  • Run command — the dev server command for Dev Preview
  • Environment variables — project-scoped env vars (with encrypted storage for secrets)
  • Default recipe — which recipe auto-runs when creating a new worktree

To share settings and project identity with your team via version control, see The .canopy/ Directory below.

Worktree Mood

Each worktree has a mood indicator — a quick visual summary of its current state:

MoodCondition
StableClean working tree with no uncommitted changes and recent activity.
ActiveHas uncommitted changes — someone (or something) is working.
StaleNo changes and the last commit is more than 7 days old.
ErrorGit problem detected (merge conflict, corrupted state, etc.).

Worktree Path Patterns

You can configure where worktree directories go in Settings > Worktree Paths. The path pattern uses template variables:

VariableDescriptionExample
{base-folder}Repository folder namemy-project
{branch-slug}Sanitized branch namefeature-login
{repo-name}Alias for base-foldermy-project
{parent-dir}Parent directory of repo/Users/you/Projects

Presets

Three built-in presets are available:

  • Subdirectory (default): {parent-dir}/{base-folder}-worktrees/{branch-slug} — creates a -worktrees sibling folder next to your project
  • Sibling Folder: {parent-dir}/{base-folder}-{branch-slug} — creates sibling folders with branch suffixes
  • Flat Sibling: {parent-dir}/{branch-slug} — creates branch-named sibling folders

If the generated path already exists, Canopy appends numeric suffixes (_2, _3, etc.) to avoid collisions.

GitHub Issue Linking

Worktrees can be linked to GitHub issues. This happens automatically when your branch name includes an issue number (e.g., feature/issue-42-fix-header), or you can attach one manually from the worktree's context menu.

When linked, the issue title becomes the worktree card's primary headline, with the branch name moving to a secondary row below. See GitHub Integration for more details.

Pinning Worktrees

Pin important worktrees to keep them at the top of the sidebar regardless of sort order. Just click the pin icon on any worktree card. Pinned state persists across restarts.

Project Pulse

Project Pulse is an activity heatmap that gives you a personal view of your commit rhythm. It appears on the empty panel grid when no terminals are open for the active worktree, showing your git commit density over time alongside streak tracking and optional GitHub health signals.

Pulse reads directly from your local git log, so your commit history never leaves your machine. The optional GitHub Health Signals row does make API requests when a token is configured (see below), but the heatmap itself is entirely local.

The feature is enabled by default. To turn it off, go to Settings > General and toggle "Show activity heatmap on the empty panel grid". See Settings for more on that toggle.

Activity Heatmap

The heatmap renders your commit history as a grid of color-coded cells, arranged left-to-right from oldest to newest. Each cell represents one day, with four heat levels indicating commit density: empty (no commits), low, medium, high, and full activity.

Three range options are available via the pill toggle in the card header: 60 days (default), 120 days, and 180 days. Switching the range clears cached data and refetches from the git log.

Days before the repository's first commit are filtered out entirely, so the heatmap starts from the actual beginning of the project rather than showing empty cells for dates that predate it. The most recent day with commits gets a subtle accent ring to help you orient quickly. Hovering any cell shows a tooltip with the date and commit count.

Note

You may notice some zero-commit days highlighted with a red tint. These are missed day markers, which appear when a day with no commits is surrounded by activity within a 4-day window on either side. They highlight gaps in otherwise active periods, not days you were simply away from the project.

Coach Line

A single line of motivational text appears below the heatmap, adapting to your current activity state:

ConditionMessage
Commits made today"3 commits today — nice." (actual count)
Active streak, no commits yet today"One small commit today keeps your streak going."
Active in last 7 days, no streak"Momentum's building: 4 active days this week."
Fallback"Make a tiny win: ship one small change today."

Summary Row

Below the coach line, a stats row shows key metrics for the selected range:

  • Commit count in the selected range
  • Active days ratio (e.g. "47/60 active days")
  • Streak with flame icon and day count (when streak is longer than 1 day)
  • Uncommitted files with insertions/deletions count (when there are uncommitted changes)
  • Branch delta vs base branch showing ahead/behind commits, files changed, and insertions/deletions (only on non-default branches)

Streak

The streak counter tracks consecutive days with at least one commit. When your streak reaches 2 or more days, a flame icon appears in the summary row alongside the day count. The flame color shifts through tiers as your streak grows:

DaysColor
1–7Amber
8–14Light orange
15–29Orange
30–59Red
60–119Darker red
120–239Violet
240+Accent (legendary)

At milestone thresholds (30, 60, 120, and 240+ days), a brief animated flame effect plays the first time you see the card that day. The animation respects prefers-reduced-motion and falls back to a static glow.

Tip

The streak is a motivational nudge, not a target. It tracks your natural rhythm and is visible only to you. If the gamification aspect isn't your thing, turning off Project Pulse in settings removes the streak display along with the rest of the card.

GitHub Health Signals

When the active project has a GitHub remote configured and a personal access token added in Settings > GitHub, a row of status chips appears on the Pulse card showing live project health data:

  • CI status (passing, failing, pending, or no CI configured)
  • Open issues count
  • Open PRs count
  • Latest release tag with relative time
  • Security alerts count (Dependabot)
  • Merged PR velocity within the selected time range

Each chip links to its corresponding GitHub page. If no GitHub remote is configured, a hint prompts you to connect one. For token setup, see GitHub Integration.

Loading and Error States

While fetching data, the card shows a skeleton with shimmer animation. If the repository has no commits yet, a placeholder message reads "New repository — make your first commit to start tracking activity."

On fetch errors, Pulse retries automatically up to 3 times with exponential backoff. A manual refresh button in the top-right corner of the card lets you re-fetch both the heatmap data and GitHub health signals at any time.

The .canopy/ Directory

Canopy supports an optional .canopy/ directory at the root of your repository — similar to .vscode/ or .devcontainer/ — for storing project configuration alongside your code. When present, Canopy reads these files on project open so any team member who clones the repo gets the same project identity, settings, and recipes automatically. Environment variables and secrets are never written to these files and must be entered locally on each machine.

Enabling In-Repository Settings

To start writing settings into .canopy/, open Project Settings > General (right-click the project name in the sidebar, or use the gear icon in the project toolbar) and scroll to the In-Repository Settings section. Toggling on Store settings in repository expands a confirmation panel listing the files Canopy will create. Click Confirm and enable to proceed.

Once enabled, Canopy writes .canopy/project.json and .canopy/settings.json immediately and keeps them in sync as you change settings. Existing project recipes are synced to .canopy/recipes/. These files are separate from the settings.json Canopy maintains in its app data directory — the in-repo files contain only the shareable subset of your project configuration.

Note

Disabling in-repository settings is non-destructive. Canopy stops writing to .canopy/, but the existing files stay in the repo. A teammate cloning the repo will still have the identity and settings applied on open.

Files Overview

FileContainsSafe to commit?
.canopy/project.jsonProject name, emoji, and colorYes
.canopy/settings.jsonShareable project settings (run commands, worktree path pattern, terminal settings, etc.)Yes
.canopy/recipes/Per-project recipes as individual JSON files (env var values are redacted; keys preserved). Synced from local recipe storage when in-repo mode is active.Yes
.canopy/config.jsonWorktree lifecycle scripts (setup and teardown commands)Yes
.canopy/*.local.jsonMachine-local overrides (reserved for future use)No — add to .gitignore

What's Shared vs. Machine-Local

When Canopy writes .canopy/settings.json, it deliberately excludes machine-specific values. The following fields are never written to the in-repo file:

  • Environment variables and secrets — project-scoped env vars stay in encrypted local storage only
  • Shell executable path — your local shell binary is not shared
  • Dev server auto-detection state — a per-machine flag
  • Project icon (SVG) — stored locally, not in the repo
  • Notification overrides — per-machine preferences

Everything else — run commands, dev server command and timeout, CopyTree settings, excluded paths, worktree path pattern, and terminal settings (shell args, default working directory, scrollback lines) — is written to settings.json and shared.

.gitignore Guidance

The enable confirmation panel shows a snippet you can copy into your .gitignore — Canopy never modifies .gitignore automatically. The snippet has two parts:

  • Safe-to-commit files (project.json, settings.json) — listed as a reference. Do not add these lines to your .gitignore if you want to commit them to the repo; the whole point of enabling in-repository settings is to track these files in git.
  • Machine-local files (*.local.json) — these should always be in .gitignore.

The only line you must add to .gitignore is:

.canopy/*.local.json

.canopy/recipes/ is also safe to commit — recipe env var values are redacted before writing, so no secrets are stored in those files.

Worktree Lifecycle Scripts

The .canopy/config.json file lets you define shell commands that run automatically when worktrees are created or deleted. This is useful for running npm install, seeding a database, or tearing down containers as part of your worktree workflow. Lifecycle scripts run automatically for every worktree with no user prompt. If you want to launch specific agents or panels conditionally, use Recipes instead.

Create .canopy/config.json manually in your repo. This file is independent of the in-repository settings toggle:

{"setup": [
  "npm install",
  "cp .env.example .env"
],
"teardown": [
  "docker compose down"
]}

Setup commands run asynchronously after a new worktree is created, with the worktree root as the working directory. You can switch to and use the worktree immediately, but tools that depend on setup completing (like npm-installed CLIs) won't be available until the spinner clears. A spinner appears on the worktree card while commands run, showing the name of the active command. Commands run sequentially, and if one fails the rest are skipped. Teardown commands run before git worktree remove. Normal deletion allows up to 120 seconds for teardown to complete; force-delete shortens this to 15 seconds. Teardown failures are logged but never block deletion.

Note

Script output is not visible in the Canopy UI. While running, the worktree card shows the active command name. On failure, you'll see a red "Setup failed" or "Setup timed out" label on the card. To capture output for debugging, redirect it to a log file in your commands:

npm install >> .canopy-setup.log 2>&1

For teardown scripts, log to a path outside the worktree (e.g. $CANOPY_PROJECT_ROOT/.canopy-teardown.log) since the worktree directory is removed after teardown completes.

Script Priority Order

Canopy checks three locations for config.json and uses the first one found:

  1. ~/.canopy/projects/<project-id>/config.json — user-level override (machine-local, not in repo)
  2. <worktree-path>/.canopy/config.json — worktree-level config
  3. <project-root>/.canopy/config.json — main repo config (shared with team)

The user-level location is useful when you need machine-specific setup steps (e.g., activating a local virtualenv) without touching the shared repo config.

Injected Environment Variables

Canopy injects the following variables into every lifecycle script command:

VariableValue
CANOPY_WORKTREE_PATHAbsolute path to the worktree directory
CANOPY_PROJECT_ROOTAbsolute path to the main repository root
CANOPY_WORKTREE_NAMEThe branch/worktree name
CItrue
NONINTERACTIVE1
GIT_TERMINAL_PROMPT0
DEBIAN_FRONTENDnoninteractive

Your project's environment variables are not injected into lifecycle scripts.

Timeouts and PATH

Config Validation

If config.json contains invalid JSON or doesn't match the expected schema (only setup and teardown string arrays are recognized), Canopy logs a warning and skips that config file. The next location in the priority chain is tried instead. If all three levels are invalid or missing, no lifecycle scripts run.

When scripts don't execute as expected, check Canopy's log file (Settings > Troubleshooting > Open Log File) for config parse warnings.

New Worktrees Inherit .canopy/

When Canopy creates a new worktree, it copies the .canopy/ directory from the main repo into the new worktree directory. This seeds the worktree with the shared config so lifecycle scripts and identity are available immediately. Existing files in the destination are never overwritten — if a worktree already has a .canopy/config.json, it is preserved as a worktree-level override.

This also means that once a worktree has been created, future changes to the main repo's config.json won't automatically apply to it — the copied worktree-level file takes priority per the order above. Delete the worktree-level .canopy/config.json if you want the worktree to inherit updated repo-level scripts.

For creating, switching, deleting, and comparing worktrees, and for details on the worktree card UI, see Worktrees.