---
title: Edit a project
---

# Edit a project

## What it is

Once a project is in the sidebar, every property except its underlying folder path is editable in place. You can rename the sidebar label, change or clear its color bar, give it an **emoji or icon**, swap or remove a custom image, pin it to the top of the list, **move it into a sidebar group (or spin up a new group on the spot)**, hide the git-branch indicator in session headers, and toggle session isolation. Renaming changes only the **display name** in Omniscio — your folder on disk keeps its original name. The folder path shows under _More options_; to point the project at a different location use **Move to a new folder…**, which re-points the project (and its sessions) to the new folder — or merges it into an existing project already there (covered in _Moving a project to a new folder_ below).

The **Edit Project** dialog is **two-tier** so the everyday choices aren't buried under the advanced ones. It opens on a friendly **Identity** step — the **project name**, a **Group** selector (assign the project to a sidebar group, or create a new group inline without leaving the window), and an **Appearance** control that stays **collapsed by default** (showing just the current icon and a chevron) so the swatch grids don't dominate the window — click the **Appearance** row to expand it (color, plus an optional emoji / icon / custom image, with a live preview of how the project will look). A **More options →** button reveals the second step (folder/move, session isolation, hide-branch, deploy profile, default provider/model, MCP tools, bug-intake), with a **← Back to appearance** button to return. The same fast icon/pin/hide-branch/folder actions also live on the project's right-click menu so you don't have to open the dialog for them.

The right-click menu keeps the high-frequency actions flat at the top level — **Edit Project**, **Edit Notes**, **Pin to Top** / **Unpin**, and the red **Remove Project** — and groups the less-frequent ones into two hover/click flyout submenus to keep the menu short: a **Files** submenu (Open Project Docs, Show in Explorer, Copy Path) and an **Appearance** submenu (Detect Icon, Choose or Replace Icon…, Remove Icon, and the Show/Hide-branch-in-header toggle). Run Recipe remains its own submenu as before. Point at a submenu label (it has a `›` chevron) and the nested items fly out to the side; click a leaf to run it and the whole menu closes.

## Where to find it

### How to use it

1. **Open the Edit dialog.** Right-click the project's row in the projects sidebar. The first item in the menu is **Edit Project** (pencil icon). Click it. (You can also middle-click the row to remove it — that is the delete shortcut, not edit.)
2. **Change the name.** On the opening **Identity** step the Project Name field is auto-focused. Type a new label and press Enter or click **Save Changes**. Renaming never touches the folder path — that, and relocating, live under **More options →** (see _Moving a project to a new folder_ below).
3. **Pick a group (optional).** Just under the name is the **Group** dropdown — the same control the Add Project dialog uses. It lists every existing sidebar group (divider): pick one to move the project into it, leave it on **None** to keep it ungrouped, or pick **+ Create new group…** to name a brand-new group without leaving the window. The move is applied when you click **Save Changes** (not immediately), so **Cancel** or **Esc** leaves the project in its current group. If the move fails on the backend, the error shows inline and the window stays open (no false "updated"). Other ways to group projects (drag, or a divider's Add Project) live in [reorder-projects.md](reorder-projects.md).
4. **Set the appearance (color · emoji · icon).** The **Appearance** control sits below the group and is **collapsed by default** — you see the current icon and a chevron. Click the **Appearance** row to expand it (this keeps the swatch grids from dominating the window). Expanded, it shows a live preview tile plus:
   - **Color** — the same swatch row as Add Project: click a preset, **Custom** for a hex value, or **None** to clear it. The vertical color bar on the sidebar row updates when you save.
   - **Icon** — a curated quick-grid of common **emoji** and **Lucide icons** for one-click picks, plus a **More emoji…** button that opens the full emoji picker (search + every emoji — the same picker used in SMS and Team Chat) so you can choose _any_ emoji, not just the ones in the grid. Click a grid swatch, or open **More emoji…** and pick one, to set it (it persists immediately and shows in the preview at once); click the **No icon** (∅) swatch to clear back to the color dot. An emoji/icon takes precedence over the color bar everywhere the project appears.
   - **Custom image…** — opens the native file picker filtered to image types (`.png`, `.svg`, `.ico`, `.jpg`, `.jpeg`, `.webp`, `.gif`), defaulting to your project folder. Omniscio stores the absolute path; if you move that file later the icon breaks (silent fallback to the default).
   - **Auto-detect** — re-runs the auto-detector, which scans your folder for an icon or logo: the usual spots (`favicon.ico`, `public/icon.svg`, …) **plus** product-named logos like `omniscio-logo.png` or `youtube-logo.png` sitting in `public/` or the project root. Useful if you added a logo to the repo after creating the project. Even without clicking this, a project that's only showing a generic file-type badge is upgraded to its real logo automatically on the next app start once one is present — while an emoji/icon you picked by hand is never overridden.
5. **Open More options.** Click **More options →** to reveal the advanced, rarely-touched settings (folder/move, isolation, hide-branch, deploy profile, default provider/model, MCP tools, bug-intake). **← Back to appearance** returns to the Identity step.
6. **Toggle "Hide git branch in header".** (Under More options.) Sessions normally show the project's current git branch in their session header (e.g. `main`, `feat/foo`). Flipping this on suppresses that indicator for every session in this project. Useful for branchless folders or when the branch info is just visual noise. There's also a faster shortcut: right-click the project → **Appearance** → **Show branch in header** / **Hide branch in header** flips it without opening the dialog.
7. **Toggle "Isolate new sessions".** (Under More options.) When on, every new session in this project runs against its own copy of the folder (a worktree-style sandbox), and changes merge back when the session ends. Off by default. The toggle is wired to `IPC.PROJECT_SET_ISOLATION` and applies on the next session spawn, not retroactively. See [Session isolation](session-isolation.md) for what that copy contains and how it is cleaned up.
8. **Set a default model and thinking level (Claude projects).** (Under More options.) Projects whose default provider is Claude get two dropdowns — **Default model** and **Default thinking level**. Pick a value and every new session in this project starts on it; leave either on **Global default** to inherit your app-wide setting (Settings → Accounts). They save immediately (you don't need to hit Save Changes) and apply to the next session you start — a session can still override both from its own header before its first message (see [start-a-new-session.md](start-a-new-session.md)). A **Default thinking level** left on **Global default** inherits the app-wide thinking default, which can itself vary **per model** (Settings → Session → Per-model thinking level — e.g. Opus → Max, Haiku → Low); a project thinking default you set here still wins over that per-model default. See [per-model-thinking-level.md](per-model-thinking-level.md). The dropdowns are hidden for non-Claude projects (other engines pick their own model) and for virtual projects.
9. **Pin or unpin.** Right-click → **Pin to Top** moves the project into a fixed pinned section above the dividers and ungrouped projects. The pinned section has its own drag-and-drop order. Right-click again → **Unpin** sends it back into the regular list at its original `displayOrder` position. (The same Detect/Choose/Remove-icon actions also remain on the right-click **Appearance** submenu for one-click access without opening the dialog.)
10. **Save and close.** The dialog's **Save Changes** button only writes what actually changed — name, color, provider, and the **group** (a no-op close keeps your previous data untouched); icon picks persist immediately, independent of Save. A group change is applied as a **move** (the project hops to the chosen group) separately from the name/color write, and if that move fails the error is surfaced inline and the window stays open instead of a false "updated". Other backend errors (e.g. blank name) also appear inline above the action buttons — if the name is blank when you Save from the Options step, it jumps you back to Identity to fix it. Press **Esc** or click the backdrop to discard your name/color/group edits.

The Edit dialog hides nothing for virtual projects either — but real-world editing of a virtual project (Skills, Recipes, Cron Jobs, etc.) is mostly cosmetic since their behavior comes from the Omniscio built-ins, not the project record. The whole **Files** submenu and the whole **Appearance** submenu are gated to **non-virtual** projects — for a virtual project those two flyout triggers simply aren't rendered (so every item they would contain — Show in Explorer, Open Project Docs, Copy Path, Detect Icon, Choose or Replace Icon, Remove Icon, Show/Hide branch in header — is absent), and only the flat top-level items (Edit Project, Edit Notes, Pin/Unpin, Remove Project) remain.

### Moving a project to a new folder

Moved a repo on disk, or want to relocate a project? Use **Move to a new folder…** — the button just under the Folder Path in the Edit Project dialog, and the **Locate moved folder…** button on the red _"project folder is missing"_ banner. It opens a folder picker, then does one of two things automatically based on what's at the destination:

- **No project there yet → it re-points in place.** The project's folder changes and every session — plus recipes, schedules, and trackers — comes along, because they're tied to the project's stable id, not its path. Sessions that are still running stay pinned to their original folder so their `--resume` keeps working; only new sessions use the new folder. Nothing is copied or deleted on disk — Omniscio just updates where the project points.
- **A project already lives at that folder → it offers a guided merge.** You get a confirmation showing exactly how many sessions will move; a one-time safety backup of the database is taken first; then everything from this project is re-homed into the one already there, and this (now-empty) project is retired. This is the clean way to fix a split you created earlier by adding the same repo twice. The merge runs as a single all-or-nothing transaction, so it can never leave your data half-moved.

Built-in projects (Skills, Recipes, channels, the KMS vault, etc.) can't be moved — they aren't real folders, so the button is hidden for them.

**Moved the folder but left a junction (or symlink) at the old path?** Then there's nothing to re-point. Omniscio treats a project folder reached through a junction as the same folder as its real location — git reports the real path, and Omniscio reads it back in the spelling the project was saved under — so agents' worktrees, Work in Flight, worktree cleanup and session ownership all keep matching. Keep the junction in place for as long as the project stays saved under the old path: remove it and the old path is simply gone, which is the _"project folder is missing"_ case above. There's no need to run `git worktree repair` because of the junction.

### Worktree location (in-development, Lab-gated)

> Hidden until you enable **Per-project worktree location** in **Settings → Lab** (or it ships on by default). Documented here so it's discoverable while gated.

When session isolation is on, Omniscio creates a git **worktree** per session. **Worktree location** — a row under _More options_, shown only for real git repos — lets you choose WHERE those worktrees are created for this project. The point is disk hygiene: you can keep heavy worktree churn (builds, `node_modules`, parallel checkouts) off the same physical disk as your database, so they stop competing for I/O. The row shows the current base folder and where the value comes from (a custom location you set · the repo's committed default · the built-in default), a **Browse…** button (native folder picker), and **Reset** (back to the default). Pick a folder _inside_ the repo and it warns you — worktrees there make repo-wide searches crawl, so a folder outside the repo is best. The warning persists across reopens, not just right after the pick.

Your choice is **private to this machine**: it's written as a git-ignored `worktree-locations.local.json` at the repo root, and Omniscio marks it ignored in the repo's _local_ git config so it can never be committed (your teammates never inherit your path). That one file is read by **everything** that creates or cleans up worktrees — the app, the dev-pipeline skill, the cleanup reaper, and dependency-sync — so the choice takes effect everywhere, not just inside the app. **Reset** deletes the file and falls back to the repo's committed default (or the built-in `<repo>-worktrees` sibling).

**How it works.** The row is [ProjectWorktreeLocationField.tsx](../../src/renderer/src/features/projects/ProjectWorktreeLocationField.tsx): it calls `PROJECT_GET_WORKTREE_LOCATION` on open, `PROJECT_PICK_WORKTREE_LOCATION` for the native folder picker, and `PROJECT_SET_WORKTREE_LOCATION` (with `worktreeLocation: null` to reset) — handled in [project-handlers.ts](../../src/main/ipc/project-handlers.ts) via [worktree-location-override.ts](../../src/main/services/worktree/worktree-location-override.ts), which writes BOTH the `createIn` base AND appends it to the scan list (so the reaper + dependency-sync follow the new location) with an atomic temp-then-rename. The three polyglot readers (app / script / PowerShell) prefer the `.local.json` override over the committed `worktree-locations.json`, kept byte-identical by a parity test. Full invariants + the tests that lock them: [worktree-locations-contract.md](../../.claude/memory/contracts/worktree-locations-contract.md). CLI twin: `POST /project/:id/worktree-location` (body `{ worktreeLocation: <path> | null }`).

### AI icon suggestion (in-development, gated)

> Hidden until you enable **AI Project Icons** in **Settings → Lab** (or it ships on by default). Documented here so it's discoverable while gated.

For a project that only shows a generic **type badge** (md, js, …) — i.e. no favicon/logo could be detected, so it fell back to the type icon — Omniscio can ask AI to pick a relevant icon for it. When the feature is on, the appearance picker gains a **"Suggest with AI"** button (next to Auto-detect) that runs a quick one-shot AI call over the project's name + detected type + a short (size-capped) README/description peek and applies the single best-fitting icon from the **full free Lucide library** (~1,900 icons). A **"Fill missing icons with AI"** button does the same in bulk for every real project that still shows a type badge, behind a confirm that shows the count first so the cost is never a surprise. It is **on-demand only** — nothing spends on AI unless you click — and it never overwrites an icon you picked by hand or a real detected logo. An AI-picked icon is a normal `lucide:<name>` token, so it is "sticky" like a hand pick: a favicon appearing later won't silently replace it (Auto-detect re-detects to override).

**How it works.** `PROJECT_SUGGEST_ICON` (single) and `PROJECT_SUGGEST_MISSING_ICONS` (batch) in [project-handlers.ts](../../src/main/ipc/project-handlers.ts) call [icon-suggestion.ts](../../src/main/services/project-icon/icon-suggestion.ts) → `aiSuggestionService.suggestIconName` (the cheap-eligible `icon-suggest` label; robust parse + one retry + graceful null — [ai-icon-suggestion.ts](../../src/main/services/ai/ai-icon-suggestion.ts)), then write the picked `lucide:<name>` through the SAME `isAllowedProjectIconToken` boundary as a manual pick. The full Lucide set validates against a generated name list ([lucide-icon-names.generated.ts](../../src/shared/lucide-icon-names.generated.ts), regenerated by `node scripts/gen-lucide-icon-names.mjs`) and renders via lucide's lazy `DynamicIcon` so the app never static-imports the whole library — see [ProjectLucideIcon.tsx](../../src/renderer/src/components/ui/ProjectLucideIcon.tsx). The batch targets exactly `projectNeedsAiIcon(project)` (a real, non-virtual project whose icon is `null` or a `__type:` badge) — the single source of truth shared with the picker's "missing" count so they can't drift. Both channels are **desktop-only** (blocked over the mobile/web WS bridge) and gated via `isUnreleasedFeatureVisible('ai-project-icons')`. Full invariants + the tests that lock them: [project-icon-token-contract.md](../../.claude/memory/contracts/project-icon-token-contract.md) (I6).

## How it behaves

### How it works

The dialog is [EditProjectDialog.tsx](../../src/renderer/src/features/projects/EditProjectDialog.tsx), portaled to `document.body` so the modal isn't clipped by the sidebar's backdrop blur. It renders inside [ProjectListItem.tsx](../../src/renderer/src/features/dashboard/ProjectListItem.tsx) and opens when the user picks **Edit Project** from the right-click portal menu (also defined in `ProjectListItem.tsx`). On submit, the dialog calls `useProjectStore().updateProject()`, which invokes the `PROJECT_UPDATE` IPC channel; the handler in [project-handlers.ts](../../src/main/ipc/project-handlers.ts) validates against `updateProjectSchema` from [ipc-schemas.ts](../../src/shared/ipc-schemas.ts), refuses path changes pointing at non-existent folders, then runs `updateProject()` in [queries-projects.ts](../../src/main/db/queries-projects/projects.ts) — a `buildSetClause` over the editable columns (`name`, `folder_path`, `color`, `icon_path`, `hide_branch_in_header`) and a single `UPDATE projects SET ... WHERE id = ? AND is_deleted = 0`. Booleans coerce to `0/1` before reaching better-sqlite3, which doesn't accept JS booleans on integer columns. The handler emits `PROJECTS_CHANGED` so the renderer re-fetches. The per-project **default model** and **default thinking level** are the exception to that PROJECT_UPDATE path: like the per-project MCP defaults, they aren't `projects` columns but entries in the `projectDefaultModels` / `projectDefaultThinkingLevels` settings maps (keyed by project id), so the dialog persists them immediately via a silent `settings:update` rather than through `PROJECT_UPDATE`. At spawn, Omniscio resolves session override → project default → global default. The **Group** dropdown is the shared [ProjectGroupPicker.tsx](../../src/renderer/src/features/projects/ProjectGroupPicker.tsx) — the same control Add Project renders (extracted so the two dialogs can't drift). It's a third exception to the `PROJECT_UPDATE` path: a group change routes through `useProjectStore().moveProjectToDivider()` → the `PROJECT_MOVE_TO_DIVIDER` channel, applied on Save separately from the name/color write. Because `moveProjectToDivider` optimistically rolls back and sets a store `error` rather than throwing on a DB-level failure, the dialog re-reads `useProjectStore.getState().error` after the move (mirroring its post-`updateProject` check) and surfaces it inline — so a failed move keeps the window open instead of firing a false "updated" toast. The **Appearance** section is collapsed behind a `ProjectIcon` + `ChevronDown` disclosure button (default-collapsed via a `showAppearance` state) that reuses the existing `ProjectAppearancePicker` unchanged when expanded.

The **Files** / **Appearance** flyouts are a purely presentational regrouping. They render through a small reusable `ProjectMenuSubmenu` component (its own sibling file [ProjectMenuSubmenu.tsx](../../src/renderer/src/features/dashboard/ProjectMenuSubmenu.tsx), composed by `ProjectListItem`): a `role="menuitem"` trigger button with `aria-haspopup="true"` / `aria-expanded`, a `›` (`ChevronRight`) affordance, and the original menu items moved verbatim into its children. The trigger's `onClick` sets the open flag to `true` (it is an open, not a toggle, so a programmatic click in tests deterministically reveals the children); a 200 ms close timer on mouse-leave and a viewport-edge clamp via `getBoundingClientRect()` handle the hover UX. No IPC channel, handler, schema, or `queries-projects.ts` call changed — the icon/branch/path actions still invoke exactly the same channels they did when they were flat; only their DOM nesting moved. The non-virtual gate is the same `!isVirtualProject(project.folderPath)` guard that previously wrapped each item individually, now hoisted to wrap the whole submenu.

Pin/unpin go through the dedicated `PROJECT_PIN` and `PROJECT_UNPIN` channels, which call `pinProject(id, true|false)` in `queries-projects.ts` — that flips `is_pinned` and bumps `updated_at`. The renderer's `listProjects()` query orders by `is_pinned DESC, display_order ASC`, which is why pinned projects always render at the top of the list. Icon mutations route to **four** channels (`PROJECT_DETECT_ICON`, `PROJECT_CHOOSE_ICON`, `PROJECT_CLEAR_ICON`, `PROJECT_SET_ICON_TOKEN`) — all four write the single `icon_path` column. The choose-icon handler opens an Electron `dialog.showOpenDialog` filtered to image extensions, parented to `BrowserWindow.getFocusedWindow()` so the picker appears in front of the Omniscio window (without the parent, Windows opens the dialog behind the app), and stores the resulting absolute path. The same parent-window pattern applies to `DIALOG_OPEN_FOLDER` and `DIALOG_OPEN_FILE`. The **emoji/Lucide pick** is `PROJECT_SET_ICON_TOKEN`: `icon_path` now stores either a real image path OR a self-describing **token** (`emoji:📥` / `lucide:Inbox`). The curated grid sends a built-in token; the **More emoji…** button opens the shared full emoji picker (`EmojiPickerPopover`) so any emoji can be chosen. Because that column is read on the privileged `project-icon://` origin, the renderer is forbidden from writing a raw path (RT-F082) — so this channel accepts only a self-describing token: **any genuine emoji** (validated by Unicode shape via `isAllowedProjectIconToken`, so the whole picker works) or **any real Lucide name** — the full library, checked against a generated name list (the AI-suggestion feature below picks from it; the manual quick-grid still offers just the curated subset) — never a raw path. The icon-maintenance sweep skips tokens so it never mistakes one for a missing file and wipes it. Full invariants + the tests that lock them: [project-icon-token-contract.md](../../.claude/memory/contracts/project-icon-token-contract.md). The hide-branch toggle on the right-click menu is the same `PROJECT_UPDATE` call as the dialog, just inlined with the inverted current value: `ipc.invoke(IPC.PROJECT_UPDATE, { id, hideBranchInHeader: !project.hideBranchInHeader })`. Session isolation is its own `IPC.PROJECT_SET_ISOLATION` channel that flips `isolation_enabled` immediately when the dialog toggle changes — independent from the form's Save button so users see the worktree behavior on the very next spawn.

**Move to a new folder** is a separate flow, not part of `PROJECT_UPDATE`. The button calls the `PROJECT_RELOCATE` channel via the shared [useRelocateProjectAction.ts](../../src/renderer/src/features/projects/useRelocateProjectAction.ts) hook (reused by both the Edit dialog and the folder-missing banner) after a native folder picker (`PROJECT_PICK_RELOCATE_FOLDER`). The handler routes to the engine in [relocate-project.ts](../../src/main/services/project/relocate-project.ts): an empty destination re-points `folder_path` in place (sessions follow via their stable `project_id`; still-live sessions are pinned to the old cwd for `--resume` safety, and the few rows keyed by the path STRING — MCP overrides, council paths — are swept). An occupied destination is a two-phase, backed-up MERGE (a `merge-needs-confirm` preview first, then a `VACUUM INTO` backup, then one atomic transaction) that re-parents every project-scoped row into the survivor — the row set is derived by COLUMN NAME so a future `project_id` table can't silently be missed (a build-failing coverage guard enforces it), with "prefer the survivor on a uniqueness conflict" via `UPDATE OR IGNORE` + `DELETE`. Full invariants + the test that locks each: [project-relocate-contract.md](../../.claude/memory/contracts/project-relocate-contract.md).

## For agents

### CLI parity

The same cosmetic edits the dialog makes (rename, recolor including clearing, icon path, pin/unpin, hide-branch, move-to-divider) are also reachable over HTTP via `PATCH /project/:id` on the CLI control server. The CLI route accepts the same partial body the IPC handler does — `name`, `color` (nullable, pass `null` to clear the tint), `hideBranchInHeader`, `isPinned`, and `dividerId` (nullable, pass `null` to remove from the current group). `iconPath` is **not** accepted: the route rejects it with `400` and names the way to set an icon for real — `POST /project/:id/icon` with an `emoji:`/`lucide:`/`brand:` token, or `POST /project/:id/detect-icon` to derive one from the project folder. A caller-supplied path stays refused because `projects.icon_path` feeds the privileged `project-icon://` protocol (RT-F082). All cosmetic edits apply immediately (no approval queue) because they're revertible personal preferences with no destructive cascade. Detect-icon, choose-icon (file picker), and isolation toggle are dialog-only — the CLI surface only exposes fields that map to the underlying `projects` columns, not flows that need an Electron dialog or a worktree spawn pipeline. See [cli-control.md](cli-control.md) "PATCH /project/:id" for the full schema.

## Related

- [add-a-project.md](add-a-project.md) — adding a project (sets the same name/color/icon fields the Edit dialog tweaks afterwards)
- [delete-a-project.md](delete-a-project.md) — removing a project from the sidebar (different from editing, soft-delete with undo)
- [reorder-projects.md](reorder-projects.md) — pin, drag, and group order changes
- [branch-header.md](branch-header.md) — what the git-branch indicator on session headers is and why you'd hide it (forward link — separate page covering the indicator itself in detail)
- [projects-sidebar.md](projects-sidebar.md) — the Omniscio built-ins divider that owns the virtual projects you can also edit
- [cli-control.md](cli-control.md) — the CLI control server and its main endpoints, including `PATCH /project/:id` for the same cosmetic edits this dialog makes
- [slow-computer.md](slow-computer.md) — why worktree churn can contend for disk I/O (the reason the _Worktree location_ control exists)
