---
name: configure-pappardelle
description: Interactively configure Pappardelle by editing .pappardelle.yml and .pappardelle.local.yml. Helps add profiles, keybindings, hooks, watchlists, and more. Use when the user asks to configure, customize, or tweak their Pappardelle setup.
---

# /configure-pappardelle — Interactive Configuration Editor

Help the user configure their Pappardelle setup by editing `.pappardelle.yml` (shared, checked into git) and/or `.pappardelle.local.yml` (personal, gitignored).

## Getting Started

1. **Find the config files** at the git repository root:

```bash
REPO_ROOT=$(git rev-parse --show-toplevel)
echo "Repo root: $REPO_ROOT"
ls -la "$REPO_ROOT/.pappardelle.yml" "$REPO_ROOT/.pappardelle.local.yml" 2>/dev/null
```

2. **Read the existing config** (if it exists). If no `.pappardelle.yml` exists, suggest running `/init-pappardelle` first.

3. **Ask what the user wants to configure** using `AskUserQuestion`:

Options:

- **Add or edit a profile** — create a new project profile or modify an existing one
- **Assign emojis to profiles** — suggest a ticket-rail emoji for each profile (STA-924)
- **Configure keybindings** — add, change, or remove keyboard shortcuts
- **Set up workspace init commands** — commands to run after worktree creation (`post_workspace_init`)
- **Set up workspace deinit commands** — commands to run before workspace deletion (`pre_workspace_deinit`)
- **Configure issue watchlist** — auto-create workspaces for assigned issues
- **Edit local overrides** — personal keybinding overrides in `.pappardelle.local.yml`
- **Change providers** — switch issue tracker or VCS host
- **Configure Claude settings** — initialization command, permissions
- **Configure agent profiles** — run codex or another CLI in the agent pane, or set model and effort
- **Set the companion pane command** — what runs in the right pane (`companion_command`; default gitui)
- **Set the IDE command** — what the `d` key opens (`ide_command`; default Cursor)

Then follow the appropriate section below based on their choice.

### Proactive emoji suggestion

Before showing the menu: if the config has >2 profiles and none of them have
an `emoji:`, ask once whether to assign emojis (see _Configuring Profile
Emojis_). If yes → jump there; if no → continue to the menu and don't ask
again this session.

## Configuring Profiles

Profiles define project-specific workspace behavior. Ask these questions with `AskUserQuestion`:

1. **Profile name**: kebab-case slug (e.g., `my-app`, `backend-api`)
2. **Display name**: human-readable (e.g., `My iOS App`, `Backend API`)
3. **Keywords**: words that auto-select this profile when creating workspaces (e.g., `ios`, `app`, `swift`). Include the issue prefix with hyphen if applicable (e.g., `MOB-`). Keywords only _preselect_ — since STA-1837 the TUI shows every profile in a picker on the first Enter, so the list doesn't have to be exhaustive; cover the words the user actually types and let the arrow keys handle the rest
4. **Emoji** (optional): suggest one via the resolution flow in _Configuring Profile Emojis_ below. If the user already has emojis on other profiles, always ask — otherwise skip unless they express interest.
5. **Team prefix override**: if this profile uses a different issue key prefix than the global `team_prefix`
6. **Tracker project routing** (`tracker_projects`, optional): a list of tracker projects that map to this profile — Linear project names, Jira project names/keys (an entry like `KAN` matches the same as `Pappardelle Testing`, STA-1649), or beads ID prefixes (`myproj` matches `myproj-a1b2`). Pappardelle uses it for two things: (a) when an existing issue is fetched, its project is matched against the list to auto-select the profile (both providers), and (b) when a _new_ issue is created under this profile via `idow`, `tracker_projects[0]` is resolved to a Linear project UUID and assigned automatically (STA-959; Linear-only, since `team_prefix` controls which Jira project new issues land in, and a new beads issue takes its prefix from the database it lands in). Order so the active project for new work is first; re-order when the active project rotates (e.g., once `Foo MVP` ships, move `Foo Quality` to position 0).
7. **Project type**: ask what kind of project to generate sensible defaults:
   - **iOS app**: ask for app directory, bundle ID, Xcode scheme. Generate `vars` and `commands` for xcodegen/xcodebuild
   - **Backend/API**: generate dependency install commands
   - **Frontend/Web**: generate npm/yarn commands
   - **Other**: let user specify custom commands

### Profile fields reference

```yaml
profiles:
  my-profile:
    keywords: [keyword1, keyword2]
    tracker_projects: # Optional — Linear project names, Jira project
      - 'My Project Quality' # names/keys, or beads ID prefixes. On Linear, [0] is the
      - 'My Project MVP' # default project for new issues (STA-959).
    display_name: 'My Profile'
    emoji: '🎸' # Optional — shown in the ticket rail (STA-924)
    team_prefix: PREFIX # Optional per-profile override
    agent_profile: codex # Optional — named agent profile from the top-level `agent_profiles:` map
    initialization_command: do # Optional per-profile override (agent-agnostic)
    vars: # Custom template variables
      KEY: 'value'
    vcs:
      label: 'github_label' # Label applied to PRs/MRs
    links:
      - url: '${ISSUE_URL}'
        title: 'Issue'
      - url: '${PR_URL}'
        title: 'PR'
        if_set: 'PR_URL'
    apps:
      - name: 'Xcode'
        path: '${WORKTREE_PATH}/path/to/project.xcodeproj'
        if_set: 'SOME_VAR'
    commands:
      - name: 'Build project'
        run: 'cd ${WORKTREE_PATH} && make build'
        continue_on_error: false
        background: false
    post_workspace_init: # Profile-specific init commands (run after global)
      - name: 'Setup step'
        run: 'some command'
    pre_workspace_deinit: # Profile-specific deinit commands (run after global)
      - name: 'Cleanup step'
        run: 'some command'
        continue_on_error: true
```

## Configuring Profile Emojis

Each profile can set an `emoji:` — shown as the first cell of its row in
the ticket rail. Goes in `.pappardelle.yml` (shared).

**Pick one for a profile** by trying in order:

1. **Linear project icon** (Linear projects only): `linctl project list --json`
   and look up the profile's `tracker_projects` entry. If `.icon` is a name
   like `Rocket`/`MusicNote`, translate to the matching emoji (🚀/🎵 etc.).
2. **Guess from the profile's `display_name` / `keywords`** — pick whatever
   emoji fits best (music → 🎸, bee → 🐝, infra → ⚙️, etc.).
3. **Ask** via `AskUserQuestion` with 3 candidates + "Other".

**Bulk flow** (menu option or proactive prompt): propose a mapping for all
emoji-less profiles, show it in one table, and ask apply-all / tweak-each /
skip.

Do **not** prompt for `default_emoji:` — Pappardelle auto-promotes
unmatched rows to a blank slot whenever any profile has an `emoji:`, so
rows stay aligned without it. Only touch `default_emoji:` if the user
explicitly asks for a non-blank fallback glyph.

Remind the user to quit and relaunch the TUI to see the change.

## Configuring Keybindings

Keybindings are single-key shortcuts in the Pappardelle TUI. Ask with `AskUserQuestion`:

1. **Which key?** — single character. Blocked: `j`, `k`, `n`, `q`, `?`. Allowed but shadows a built-in default: `g`, `i`, `d`, `o`, `e`, `p`, `K`
2. **What should it do?** — run a bash command (`run`), run a skill in the agent (`send_skill`, named without `/` or `$`: the agent's prefix is added), or send text to the agent as-is (`send_to_claude`, for the agent's own commands like `/compact`)
3. **Display name** — shown in the help overlay
4. **Shared or personal?** — shared goes in `.pappardelle.yml`, personal goes in `.pappardelle.local.yml`

```yaml
keybindings:
  - key: 'b'
    name: 'Build app'
    run: 'cd ${WORKTREE_PATH} && make build'
  - key: 'a'
    name: 'Address PR feedback'
    send_skill: address-pr-feedback
```

### PR-dependent keybindings

Provisioning never opens a PR — the agent does that at its first real commit —
so a workspace often has none. Any `run` that resolves a PR must fall back to
the empty string (`-q '... .number // empty'`, not `.number`, which yields the
literal `"null"`) and branch on it, either by exiting non-zero so the TUI shows
`✗ <name> failed`, or by degrading to the repo URL. Don't prompt about this
unless the binding they're adding actually consumes a PR.

### Local overrides (`.pappardelle.local.yml`)

The local file can add, override, or disable keybindings:

```yaml
keybindings:
  - key: 'V' # Add new personal binding
    name: 'Open in VS Code'
    run: 'code ${WORKTREE_PATH}'
  - key: 'X' # Override a shared binding
    name: 'Open in Nova'
    run: 'nova ${WORKTREE_PATH}'
  - key: 'r' # Disable a shared binding
    disabled: true
```

## Configuring Workspace Init Commands

`post_workspace_init` runs after worktree creation. Common patterns:

```yaml
# Copy environment files
post_workspace_init:
  - name: 'Copy .env'
    run: 'cp -n ${REPO_ROOT}/.env ${WORKTREE_PATH}/.env 2>/dev/null || true'
  - name: 'Install dependencies'
    run: 'cd ${WORKTREE_PATH} && npm install'
    continue_on_error: true
  - name: 'Background task'
    run: 'long-running-setup.sh'
    background: true
```

Ask with `AskUserQuestion`:

1. What setup steps are needed after creating a new worktree?
2. Should any steps be allowed to fail? (`continue_on_error: true`)
3. Should any steps run in the background? (`background: true`)

> **Note:** `post_worktree_init` is accepted as a backwards-compatible alias. Use `post_workspace_init` for new configs.

## Configuring Workspace Deinit Commands

`pre_workspace_deinit` runs before workspace deletion. If a command fails (without `continue_on_error`), deletion is aborted.

```yaml
pre_workspace_deinit:
  - name: 'Close issue'
    run: 'linctl issue update ${ISSUE_KEY} --state Done'
    continue_on_error: true
  - name: 'Remove worktree'
    run: 'git worktree remove ${WORKTREE_PATH} --force'
    continue_on_error: true
```

Ask with `AskUserQuestion`:

1. What cleanup should happen when deleting a workspace?
2. Should deletion be blocked if cleanup fails?

## Configuring Issue Watchlist

Auto-create workspaces for issues assigned to you.

```yaml
issue_watchlist:
  assignee: me # 'me' auto-detects, or use explicit username
  statuses:
    - To Do
    - In Progress
  labels: # Optional: filter by label
    - pappardelle
  key_prefixes: # Optional: only these issue-key prefixes (STA-*, not WAB-*)
    - STA
  max_workspaces: 5 # Optional: most workspaces this watchlist keeps open at once
```

`key_prefixes` is an allowlist of issue-key prefixes (the part before the first `-`, e.g. `STA` in `STA-123`); case-insensitive, AND-ed with `labels`. Only prompt for it when the user's tracker account spans multiple workspaces and they want a subset — otherwise omit it, since the default already watches every prefix.

`max_workspaces` is an integer >= 1 capping how many workspaces the watchlist keeps open at once (ones it spawned that are still open or spawning). Closing a workspace frees a slot; a status change doesn't. The oldest matching issues spawn first. Omit it for no limit.

**Per-profile watchlists.** A profile may carry its own `issue_watchlist` (identical fields). Resolution order: the top-level watchlist and every profile watchlist are polled **additively** — a profile one supplements, never replaces, the top-level one. A profile watchlist with no `key_prefixes` is auto-scoped to that profile's effective `team_prefix` (profile-level, else global); an explicit `key_prefixes` wins, and with no `team_prefix` anywhere it stays unscoped. Issues it spawns are forced to that profile (`idow --profile <name>`). Each watchlist has its own `max_workspaces`.

```yaml
profiles:
  chaz:
    team_prefix: CHAZ
    issue_watchlist: # watched on top of the top-level one, scoped to CHAZ-*
      assignee: me
      statuses:
        - For Pappardelle
```

Only prompt for a per-profile watchlist when the user explicitly wants a project that watches a _different_ status/label than the global watchlist — the common case is a single top-level watchlist, so don't offer this unless they ask or describe that split.

Ask with `AskUserQuestion`:

1. Which issue statuses should trigger workspace creation?
2. Should it filter by specific labels?
3. Assignee: use `me` (auto-detect) or a specific username?

## Configuring Providers

### Issue tracker

```yaml
issue_tracker:
  provider: linear # or 'jira' or 'beads'
  # base_url: https://mycompany.atlassian.net  # Required for Jira
```

**Beads** ([beads](https://github.com/gastownhall/beads), local and git-native
via the `bd` CLI) needs no `base_url` — there is no server. Set `team_prefix` to
the database's issue prefix so the Claude Code hooks can tell a beads workspace
directory (`myproj-a1b2`) from an ordinary one (`my-app`):

```yaml
issue_tracker:
  provider: beads
team_prefix: myproj
```

### VCS host

```yaml
vcs_host:
  provider: github # or 'gitlab'
  # host: gitlab.mycompany.com  # Optional for self-hosted GitLab
```

## Configuring Claude Settings

```yaml
initialization_command: do # Skill to run on new sessions, without its / or $ (agent-agnostic; top-level, NOT inside claude:)

claude: # Applies only when the agent is claude
  dangerously_skip_permissions: false # 'yolo mode'
```

The old `claude.initialization_command` spelling still works but is deprecated; never write it into new configs, and setting both spellings at one level is a validation error.

Per-profile overrides take precedence:

```yaml
profiles:
  my-profile:
    initialization_command: do-custom
```

`claude.model` / `claude.effort` / `claude.extra_args` are deprecated: model,
effort and args live on agent profiles (next section). Never write them into new
configs; when you find them, offer to move them onto an agent profile.
`claude.extra_args` come after the flags rendered from `model`/`effort`, so an
extra arg repeating `--model` or `--effort` wins today; keep its value when moving it.
Pappardelle also offers this rewrite on startup. It can copy home-config values
into the repo file, so check that diff before the user commits it.

## Configuring Agent Profiles (running codex instead of Claude)

The agent pane runs Claude Code by default. A named agent profile plus an
`agent_profile:` reference switches a profile (or the whole repo) to another
CLI. Agent profiles are separate from workspace `profiles`, which pick one with
`agent_profile:`:

```yaml
agent_profiles:
  codex:
    command: codex # Required — base binary
    args: --yolo # Optional — free-form, appended to every invocation
    resume_args: resume {session_id} # Optional — resume attempt; omit to always launch fresh
    model: gpt-5.5 # Optional — rendered as -m gpt-5.5
    effort: high # Optional — rendered as -c model_reasoning_effort=high
    # is_claude: true          # Optional — force claude treatment for a wrapper (e.g. claude-local)
  claude-sonnet:
    command: claude
    model: sonnet # Rendered as --model sonnet
    effort: medium

agent_profile: codex # Optional global default (default: claude)

profiles:
  my-profile:
    agent_profile: codex # Per-profile reference; '' clears an inherited global back to claude
  docs:
    agent_profile: claude-sonnet # A cheaper model for one profile
```

### model / effort

Set on an agent profile; unset means no flag (the CLI picks its own default).
Values aren't validated against a list, so pass whatever the installed CLI
accepts: an alias (`opus`, `sonnet`), a full id (`claude-opus-5[1m]`), and
`low|medium|high|xhigh|max` for claude's effort. Claude and codex have built-in
flag templates; any other CLI needs `model_args` / `effort_args` containing
`{model}` / `{effort}` (e.g. `model_args: --model {model}`) before it can take
a value — validation rejects a value with no template.

To give one workspace profile a different model, define another agent profile
and point that workspace profile at it. Only prompt for these when the user
asks about model/effort; an unset field is strictly cheaper than a wrong one.

Rules to apply when editing:

- `agent_profile:` values must name an entry in `agent_profiles:` (or `claude`, which is built in); validation rejects dangling references
- `args` is where skip-permissions equivalents for non-claude agents go (codex: `--yolo`) — `claude.dangerously_skip_permissions` only applies to claude
- Claude-only behavior (`--name`, the `--model`/`--effort` templates, `~/.claude.json` pre-trust, `--resume {session_id}` default) is gated on the command's basename being `claude` or an explicit `is_claude: true`
- Warn the user that non-claude agents show an `unknown` status icon in the rail — status hooks are Claude Code-only

## Configuring the Companion Pane Command

`companion_command` is the shell command run in the right pane (next to Claude). It defaults to `gitui`.

```yaml
companion_command: gitui # top-level default for every space

profiles:
  backend:
    display_name: Backend
    companion_command: make run # per-profile override
```

**Resolution order** (first defined wins): the matched profile's `companion_command` → the top-level `companion_command` → the built-in default `GIT_OPTIONAL_LOCKS=0 gitui`. An explicit empty string (`""`) means "leave a plain shell" and stops the fallthrough; an absent key falls through to the next level. The command runs verbatim — any tool works (a different git UI, a dev server, a log tailer).

**Compose multiple tools** by having the command split its own pane first. E.g. gitui on top (focused) + a plain shell on the bottom, 70/30 (gitui bigger):

```yaml
companion_command: 'tmux split-window -v -d -l 30% -c "#{pane_current_path}"; GIT_OPTIONAL_LOCKS=0 gitui'
```

`-v` stacks the new pane below, `-d` keeps focus on the top (gitui) pane, `-l 30%` sizes the bottom shell (gitui keeps the other 70%), `-c "#{pane_current_path}"` opens it in the worktree dir. The split runs inside the companion's own tmux session, so it never touches the Claude pane. Carry `GIT_OPTIONAL_LOCKS=0` over from the default — custom commands don't get it for free. Only newly-created workspaces pick up a changed `companion_command` (existing companion sessions persist).

**When _not_ to prompt:** don't raise this unless the user asks. gitui is a sensible default; most setups never touch it. Reach for it only when the user explicitly wants a different git UI, the old `lazygit` back (`companion_command: lazygit`), a split pane (recipe above), or a non-git process (server/log) in that pane — and offer the per-profile override when their need is project-specific rather than global.

## Configuring Pane Widths

`layout` sets the starting widths of the three panes in the wide (100+ column) layout, as columns or percentage strings. Any key left out keeps the default for that pane.

```yaml
layout:
  rail_width: '10%'
  claude_width: '45%'
  companion_width: '45%'
```

Claude takes whatever the rail and companion leave, so `claude_width` only matters when one of the side panes is unset. A border the user drags by hand wins for the rest of the session. Pappardelle reads `layout` at startup, so it needs a restart after an edit. Full rules are in `pappardelle-config.md` under "Pane Widths".

## Configuring the IDE Command

`ide_command` is the shell command the `d` key runs to open an editor on the selected workspace's worktree. It defaults to Cursor.

```yaml
ide_command: code "${WORKTREE_PATH}" # top-level default for every space

profiles:
  android:
    display_name: Android
    ide_command: studio "${WORKTREE_PATH}" # per-profile override
```

**When to prompt:** the user doesn't use Cursor, or `d` isn't opening what they expect. Offer the per-profile override when the choice is project-specific. A terminal editor like `vim` won't work — the command is launched detached with no terminal attached.

Available in all command templates, link URLs, and app paths:

| Variable              | Description                  | Example                    |
| --------------------- | ---------------------------- | -------------------------- |
| `${ISSUE_KEY}`        | Issue key                    | `STA-361`                  |
| `${ISSUE_NUMBER}`     | Numeric part                 | `361`                      |
| `${ISSUE_URL}`        | Full issue URL               | `https://linear.app/...`   |
| `${TITLE}`            | Issue title                  | `Add dark mode`            |
| `${DESCRIPTION}`      | Issue description            | (full text)                |
| `${WORKTREE_PATH}`    | Worktree path                | `/Users/.../STA-361`       |
| `${REPO_ROOT}`        | Git repo root                | `/Users/.../stardust-labs` |
| `${REPO_NAME}`        | Repo directory name          | `stardust-labs`            |
| `${PR_URL}`           | GitHub PR URL (may be empty) | `https://github.com/...`   |
| `${MR_URL}`           | GitLab MR URL (may be empty) | `https://gitlab.com/...`   |
| `${SCRIPT_DIR}`       | Pappardelle scripts dir      | `/path/to/scripts`         |
| `${VCS_LABEL}`        | VCS label from profile       | `stardust_jams`            |
| `${TRACKER_PROVIDER}` | Issue tracker                | `linear`, `jira`, `beads`  |
| `${VCS_PROVIDER}`     | VCS host                     | `github` or `gitlab`       |

Profile `vars` keys also become template variables (e.g., `vars: { APP_DIR: "src" }` → `${APP_DIR}`).

## Important Rules

- **Always read the existing config before editing** — use the Read tool to get current state
- **Use `AskUserQuestion` liberally** — don't guess what the user wants, ask
- **Validate after editing** — check YAML syntax is correct
- **Shared vs personal**: changes to `.pappardelle.yml` affect everyone on the team; `.pappardelle.local.yml` is gitignored and personal
- **Reserved keybinding keys**: `j`, `k`, `n`, `q`, `?` — never assign these. `g`, `i`, `d`, `o`, `e`, `p`, `K` are assignable, but each replaces a built-in default; say which default the user is giving up
- **Command fields**: every command needs `name` (string) and `run` (string). Optional: `continue_on_error` (bool), `background` (bool)
- If the user's request matches the arguments passed to this skill (e.g., `/configure-pappardelle add a keybinding for running tests`), skip the initial "what do you want to configure" question and jump directly to the relevant section
- **Restart required**: after making config changes, remind the user that any running Pappardelle TUI must be restarted to pick up the changes — press `q` to quit, then re-launch with `pappardelle`
