trailhead-work · git:20261029.304e938 · 2026-10-29 · sha256 98dd60332ee59474
trailhead-work git:20261029.304e938A
Immutable. This exact content never changes and is served at /api/v1/blob/98dd60332ee59474.
---
name: trailhead-work
user-invocable: false
description: "trailhead work cluster: the verbs that resolve tickets on the map and manage a ticket's lifecycle. Work the next frontier ticket or the one you name (work), work one ticket whole without splitting, a new whiteboard ticket or an existing one (quick), checkpoint the ticket in play (pause), resume it from its checkpoint (resume), split an oversized ticket into children (split), and run a chosen scope (one map, the whiteboard, or everything) autonomously until a stop condition or exhaustion (auto). A cohesion cluster of the trailhead skill split, loading the shared `_shared/` core; reached through the `/trailhead:<verb>` command wrappers. Not auto-invoked: it runs only when one of these verbs is dispatched."
argument-hint: "[work|quick|pause|resume|split|auto] [ticket | \"text\" | note]"
---
`trailhead-work` is the **work cluster** of the trailhead skill: the verbs that resolve tickets and manage a ticket's lifecycle. `work` takes the next frontier ticket (or the one you name) and resolves it with its type's engine; `quick` works one ticket whole without splitting (a new whiteboard ticket from text, or an existing ticket, which stays where it is); `pause` checkpoints the ticket in play; `resume` picks it back up; `split` breaks an oversized ticket into children; `auto` runs a chosen run scope autonomously, ticket by ticket, suspending the one-ticket-per-session rule until a stop condition or exhaustion. Everything lives on the GitHub Issues; the repo holds code only.
## Move into the handed checkout (`--in=`)
The panel starts `work` and `resume` from wherever the session stands and cannot move it, so it appends the pinned checkout to the args as a last word `--in=<token>` (base64url of the checkout root). When the args of `work` or `resume` end with such a word, this section runs first, before the load, the Preflight and anything else; with no such word it does not apply. `quick` never gets one.
1. Drop the `--in=<token>` word from the args: the verb, and its ticket number if any, are what is left.
2. Run `node <templates>/trailhead-checkout-in.js --in <token> --cwd "$PWD"`. `<templates>` is the first of `${CLAUDE_PLUGIN_ROOT}/templates/`, `${CLAUDE_CONFIG_DIR:-~/.claude}/trailhead/templates/`, `~/.codex/skills/trailhead/templates/` that contains the script (the same lookup as `trailhead-work-state.js`: try all three in order, skipping the first when `CLAUDE_PLUGIN_ROOT` is unset or empty). It prints one JSON line with a `status`. If the script is missing (none of the three has it: an outdated install), stop and report that the checkout could not be reached and the install needs an update: nothing is claimed, and never move by hand.
3. `invalid` -> stop and report its `reason` in one line; never move, never look for another checkout.
4. `here` -> the session is already in the checkout: no move, carry on with the args from step 1.
5. `move` -> run the helper's `cd` field verbatim as its own Bash call (a lone `cd` is what moves the session; never join it to a read or another command, and never edit the quoting). A failed `cd` stops the verb with a report. Then re-run the helper with the new `$PWD` and require `here`; anything else means the session could not move: stop and report that, with nothing claimed.
6. After a move, say in one result-oriented line which checkout the session is now working in, then continue with **Load first** and, for an explicit ticket number, the Preflight.
## Load first, in order
Right after the move above (when there is one) and before anything else, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
Config is a property of the project/map, not the working directory: an isolation workspace inherits this config, never re-resolves it from the isolated copy's own root. **For an explicit `work <n>` / `quick <n>`, the named-ticket Preflight below runs *before* this config load:** never load config, or anything else, for a ticket that turns out closed.
## Routing: verb to engine
The **first word** of the arguments is the verb; the rest is the text (a ticket number, a `"text"` seed, or a pause note).
- **`work [ticket]`** (after **Move into the handed checkout** when the args end with `--in=<token>`) to the **Work the map** engine below (Mode 2). It dispatches the chosen ticket to its type's engine in **`references/ticket-engines.md`** (`decision` / `research` / `prototype` / `build` / `bug` / `task`). Read that file when you resolve the ticket.
- **`quick [ticket | "text"]`** to the **Work one ticket whole** engine below. It runs the full `build`/`bug` cycle from `references/ticket-engines.md`, without splitting (off the map only for a new `"text"` ticket; a map ticket stays on its map).
- **`pause [note]`** / **`resume [ticket]`** / **`split [ticket]`** to the teamwork lifecycle (`resume` first goes through **Move into the handed checkout** when its args end with `--in=<token>`): `../_shared/teamwork.md` (multi-cluster, so it lives in `_shared/`). Read it and follow it.
- **`auto [map]`** to the **autonomous run** engine in **`references/auto.md`**: it runs a chosen run scope (asked at start: one map, the whiteboard, or everything; `<n>` skips the ask) ticket-by-ticket, suspending one-ticket-per-session, taking every advisory choice as delegate without the confirm gate and stopping only at the safety rail, fog, human-necessary decisions, or human interrupt. Read that file when you engage a run.
The cross-cluster situational references and the technique bodies these engines call live in `_shared/`; this cluster names each by its `../_shared/...` path where it needs one (teamwork, out-of-scope, the gh cookbook, the techniques).
## Preflight: reject a closed or missing named ticket (before anything else)
**When the verb carries an explicit ticket number** (`work <n>` or `quick <n>`), the first action (after the move into the handed checkout, when there is one), before config load, before the map load, and before any claim, scope, or isolation setup, is a single cheap state lookup of that ticket: `gh issue view <n> --json state,stateReason,title` (one call, near-free). Branch on the result:
- **Open** → proceed into the engine (config load, selection, claim, the rest).
- **Closed, or the issue does not exist** → **stop immediately and report it by name**, with its close reason where useful (`stateReason`, and the `trailhead:out-of-scope` / `trailhead:superseded` label distinguishes resolved vs out-of-scope vs superseded). Suggest the sensible next step: for a defect in already-closed work open a **new** `bug` per the new-bug-vs-reopen rule (`references/ticket-engines.md`), otherwise pick a frontier ticket. Do **not** load config, load the map, claim, or set up any workspace: a stale or mistyped closed number must be a near-instant, near-free rejection, not a full ramp-up wasted.
This guard is the **explicit-number paths only**. Bare `work` selects from the live frontier (open by construction) and `quick "<text>"` opens a fresh ticket, so neither has a prior ticket to check and both skip it.
## Work the map: `work`
The user invokes with a map (URL or number). A ticket is optional: without one, you pick the next decision.
1. Load the **map** (the low-res view, not every ticket body). **Pick which map** if the repo has several open: use the one the user named, else the active map (`.trailhead/active-map`); if neither and more than one is open, list them and ask. Set/refresh the active-map marker to the one you're working. **Named ticket:** its map becomes the active map, a whiteboard ticket leaves it alone (see **Active map** in `../_shared/multi-map.md`, under **Multiple maps on one repo**). **Codebase issue check (greenfield, offer once):** if the repo has **no `trailhead:codebase` issue** (a greenfield map never got one) and now holds **substantial code**, **don't interrupt the work to offer now**: mark the offer **pending** and surface it **at the handoff**, just before the `/clear`-first next-step block (not as a line inside it: see the **pending codebase offer** rule in `../_shared/session-handoff.md`), exactly once. On the user's yes, generate it via the **Codebase map** technique, then pin it (one of the three permanent repo pins: dashboard, codebase, conventions) and link it from the map's Notes; if the user declines, don't nag again this session. A repo that already has the issue, or is still too thin to map, skips this.
2. Choose the ticket. **If the user named a ticket, the Preflight above already confirmed it is open** (a closed, superseded, out-of-scope, or nonexistent number stopped there, before any setup ran). If the user names one, use it (a **named** ticket may be a `trailhead:whiteboard` one: `work <n>` works it whole, off the map, with the normal cycle and split allowed; use `quick <n>` for the no-split variant; a **named** ticket may also be a `trailhead:paused` one, which `work <n>` / `quick <n>` takes back). Otherwise take a frontier ticket to act on, **scoped to this map** (add its `trailhead:map-<n>` label to the frontier query when several maps are live), **never a `trailhead:paused` ticket** (it is off the frontier: `/trailhead:resume` takes it back) and **never a whiteboard ticket** (bare `work` stays on the map; the whiteboard frontier is reached via `/trailhead:whiteboard` and `quick`). Bare `work` may act on the first ticket the query returns, but that order is `gh`'s newest-first, not a priority: present several independent tickets as a **set to choose from**, not the graph's natural next step (see **Frontier order carries no priority** in `../_shared/substrate.md`). **Claim** it: assign it to yourself before any work, then re-read the assignee before starting; on a collision, stop and ask the user (see `../_shared/teamwork.md`). A claimed ticket that carries `trailhead:paused` drops it at the claim (`gh issue edit <n> --remove-label trailhead:paused`): working it is resuming it. Likewise a claimed ticket that carries `trailhead:needs-you` (an autonomous run parked it for the human) has the mark cleared at the claim: `node <templates>/trailhead-needs-you.js clear --issue <n> --repo <owner/name>` (fallback by hand: `gh issue edit <n> --remove-label trailhead:needs-you`). A failed `clear` is reported to the user in one line but never blocks the claim. If the ticket proves too big once you're in it, propose a **split** rather than grind, but never split on your own: present the children you would create and wait for the user's go-ahead before creating anything (same file).
- **First, resolve the effective isolation mode.** The `isolation:` header defaults to **`auto`** (an absent key means `auto`; the engine never writes the key). Under **`auto`**, at work-start **before any file edit or diagnostic probe**, first run the read-only **assigned-ticket pre-check** (full rule in `../_shared/teamwork.md`): other open tickets already assigned to you **pre-bias** the ask toward `worktree`/`clone` (still asking, easy neutral), and the ask is **skipped** only when the session is already inside this ticket's own linked worktree (`trailhead/t<N>`, `N` the current ticket). Then run the remembered-answer reuse: `node <templates>/trailhead-isolation-answer.js check --repo <project root> --others <csv|none|unavailable>`; on `reuse` print the one-line note and resolve as that answer without asking, otherwise (`ask` or `skipped`) ask the user once whether they will work more than one ticket at once on this machine this session, then `node <templates>/trailhead-isolation-answer.js record --repo <project root> --answer yes|no --others <csv|none>` (sequence, 8-hour idle window and exclusions in `../_shared/teamwork.md`). **No** (a lone ticket): the mode resolves to the current checkout, exactly like `none` (the scope-free check below applies). **Yes**: resolve by repo shape, a plain repo -> `worktree`, a **superrepo** (a `.gitmodules` at the checkout root declaring at least one submodule) -> `clone`; that "yes" **is** the clone gate's answer, so the resolved `clone` path proceeds without re-asking. This asks at most once per ticket work-start (a within-session ticket switch re-asks at the new ticket's work-start), and a remembered answer may suppress even that. An autonomous run (`/trailhead:auto`) never asks and never runs the pre-check; it resolves to the current checkout with one parallel slot and to a worktree per ticket above one (see `references/auto.md`). If the header carries an **explicit** `none`/`worktree`/`clone`, honour it as-is (never subject it to auto's ask): explicit `clone` keeps its own gate, explicit `worktree` is eager. See `../_shared/teamwork.md`.
- **When the resolved mode is the current checkout (`isolation: none`, or `auto` answered no), check the scope is free before starting** (this is how a submodule/package gets serialised when worktrees don't fit): scan the other in-progress tickets' `Scope:` lines, and if one **overlaps** this ticket's scope, **stop and tell the user** the scope is busy (name the ticket + holder) and offer a disjoint-scope frontier ticket or to wait. See `../_shared/teamwork.md`.
- **Write the session-ticket marker**: at the **working root** (the checkout, worktree, or clone you'll edit in), write `.trailhead/session-ticket` as **one line** `#<n> <ticket title>`. **Keep it gitignored** (add `.trailhead/session-ticket` to that repo's `.gitignore` if absent): it's per-session local state, **never committed**. It lets an external tool show "what am I on" offline (the tracker stays the source of truth), but it is not merely that cheap hint: while it is present, the git commit-msg hook also requires every commit to carry a `Refs: #<n>` trailer matching it, so a stale marker left behind would wrongly demand that trailer on unrelated commits. Remove it at Resolve/handoff (see `../_shared/session-handoff.md`); on **Pause** it may stay (you'll return) or be cleared if you release the claim. The **`.trailhead/active-map`** marker (which map this session works) follows the same rules: gitignored, per-session, never committed; see **Multiple maps on one repo** in `../_shared/multi-map.md`.
- **Sync the commit-msg hook**: at the working root, run `node <templates>/trailhead-commit-msg-sync.js --repo <working-root>` (where `<templates>` lives and what each status means: the commit-msg hook paragraph in `../_shared/teamwork.md`). Stay silent when it reports installed or current. On **upgraded**, give the user one result-oriented line: this clone's commit-msg hook was updated to the current rules. On **foreign**, one line that the user's own hook was left untouched and git does not enforce trailhead's rules here. On **skipped**, one line that nothing was set up here (a `core.hooksPath` without a trailhead hook, or a working root that is not a git repo) and git does not enforce trailhead's rules. On a missing script or a node error (missing means none of the three template dirs has it: try them all before concluding that), inspect the hooks dir (`git rev-parse --git-path hooks`) and report per the fallback in that same paragraph of `../_shared/teamwork.md` (trailhead hook present and executable: enforced but freshness unchecked, and only while node is on the PATH git runs with, update the install; present but not executable: git skips it, not enforced; foreign hook: untouched, not enforced; no hook: not enforced, update the install), never a blind "not enforced"; it never blocks the work.
- **Backfill the UAT label once per clone**: at the working root, run `node <templates>/trailhead-uat-backfill.js --repo <owner/name> --marker <working-root>/.trailhead/uat-backfill` (same `<templates>` lookup as the commit-msg sync; pass the marker as an absolute path, since the shell may not sit at the working root). It tags any open ticket with a `UAT of:` line with `trailhead:uat` (a pending UAT follow-up opened before the label existed), and writes the `.trailhead/uat-backfill` marker itself only on a clean run, so later work-starts exit `skipped` with no `gh` call. **Keep the marker gitignored** (add `.trailhead/uat-backfill` to that repo's `.gitignore` if absent): per-clone local state, never committed. Stay silent when it tagged nothing or skipped; when it tagged tickets, one result-oriented line saying how many pending UAT follow-ups now carry the label; on `failed` entries or an `error` status, one line saying the backfill did not finish and will retry at the next work-start.
- **When the resolved mode is `worktree` or `clone` (explicit, or `auto` answered yes), set up the isolated workspace NOW, before any file edit or diagnostic probe** (not later at Execute/Fix): **if the repo has submodules (a `.gitmodules`), `isolation: worktree` is escalated to `clone`** (a superproject worktree cannot safely carry submodule state; see `../_shared/teamwork.md`). For `worktree` (no submodules), create/enter this ticket's `git worktree` on a `trailhead/t<n>` branch for the package/path the ticket's `Scope:` names (a submodule-bearing repo would have escalated to `clone` above), e.g. `git worktree add ../<repo>-t<n> -b trailhead/t<n>`; for `clone`, make an independent working copy at `../<repo>-t<n>` (branch `trailhead/t<n>`) by whichever is faster: `git clone` (add `--recurse-submodules` when the repo has submodules) + the install step, or a **folder copy that brings `node_modules`** (and the submodule working trees) to skip the reinstall (then verify the copy's git is independent, `git -C <copy> rev-parse --absolute-git-dir` resolves inside it, and the same inside every submodule, else commits leak back to the original); a clone with submodules is provisioned via `git submodule update --init --recursive` (nested included), each scoped submodule pinned at the superproject's recorded commit. **If this `clone` was reached via `auto`'s work-start ask (answered yes), that ask already served as the clone gate: do not re-ask, proceed to set up the clone. Under an explicit `isolation: clone`, run the gate as follows.** **Because `clone` is heavy (a full clone + an install), gate it, every time, before cloning: ask the user whether they expect to work more than one ticket at once on this machine this session.** If **no**, **skip the clone** and work this ticket in the current checkout (isolation only exists to stop concurrent workspaces from colliding; a lone ticket has nothing to collide with, so the shared-checkout caveats apply and that is fine, and `git:` still decides the branch as usual); if **yes**, clone as above. The gate is **`clone`-only**: `worktree` is cheap, so set it up without asking. Do every subsequent step in whatever workspace you ended up in. This is the whole point of isolation: if you diagnose and edit in the shared checkout "just to start" while a concurrent workspace exists, you are already colliding. Right after creating the workspace (before any install or build step), copy the declared gitignored files into it with `node <templates>/trailhead-isolation-copy.js --from <main-checkout> --to <working-root>`, per **Gitignored files in a new workspace** in `../_shared/teamwork.md`. Set the workspace up silently (the `clone` gate-question is the one exception you voice); it's undone at Resolve when the branch integrates and the worktree/clone is removed. See `../_shared/teamwork.md`.
- **Write the work state, last** (the working root is final only now): `node <templates>/trailhead-work-state.js set --repo <working-root> --map <n> --ticket <n> --title "<title>" --step discuss`, and add `.trailhead/work-state.json` to that repo's `.gitignore` if absent. It is updated at every step change and cleared at Resolve; the canonical description, the `<templates>` lookup, and the silent-on-failure rule are the **Work state file** paragraph in `../_shared/teamwork.md`, and the step mapping per type is in `references/ticket-engines.md`.
3. **Brief the ticket and confirm scope, before the engine runs.** With the ticket claimed, read it and give the user a **brief, result-oriented summary of what resolving it will do** (its goal and the shape of the work, not a plan yet), then **ask whether to proceed or change something as an `AskUserQuestion` menu, never as free text**: **Proceed** (Recommended) first, **Change something** second (the user types the change through the menu's built-in "Other" or in a follow-up message), plus the **delegate** option (this gate is a process choice, see `../_shared/choices.md`). On a host with no structured question tool (Codex), the same options are a plain-text numbered list, per its adapter. This is a HITL confirm gate: on **Proceed**, proceed to the engine. **If the user asks for a change, hold it against the ticket.** A change **consistent** with the ticket is folded in and the engine proceeds. A change that **diverges** from the ticket is surfaced as a mismatch, and the user is **asked, never forced**, whether to (a) work the ticket **as-is**, or (b) open a **separate new ticket** for the divergent request, as an `AskUserQuestion` menu **without the delegate option** (routing a scope change is the human's, per the one-line test in `../_shared/choices.md`). Offer both and decide neither: open the separate ticket **only if the user chooses it** (via **Scope that surfaces while working a ticket**, `../_shared/surfaced-scope.md`, the same capture-or-split path used for scope that surfaces mid-work), never automatically. **If the user defers the choice rather than settling it here, mark the divergent follow-up pending for the Pending follow-up guard (`../_shared/session-handoff.md`), so it is re-posed at the handoff rather than lost.** **Under an autonomous run (`auto`) this is a process confirm gate**, so it follows the advisory boundary in `references/auto.md`: `auto` proceeds (takes the delegate option) without stopping to brief.
- **Skip the confirm on a ticket this session already shaped.** Skip the brief menu when, in this same session (before any `/clear`), (a) the agent created this ticket from the user's own request for it, one ticket at a time (a capture `todo`/`bug`/`idea`/`seed`, `ticket`, `quick "<text>"`, or a `split` child the user approved), or (b) a Grilling round on this ticket concluded (`grill <ticket>`): the user just stated or settled its scope, so asking again is redundant. Not a trigger: tickets created in bulk as a side effect (`new`/`adopt` charting, `inbox` triage, follow-ons spawned by a grill on another topic), where no one confirmed this ticket's own scope. Skipping replaces the menu with a **one-line**, result-oriented note (e.g. "working <name> (#n), scope as just agreed"), so the user can still interrupt with a change; a change typed then follows the same consistent/divergent handling as the menu's **Change something**. Still asked: a ticket created or grilled in an earlier session, by someone else, or before a `/clear` (a fresh session has no memory of it), a `resume`d ticket, a ticket only read or mentioned this session. `auto` is unchanged (it already takes the confirm as delegate). The evidence is the session's own context, never inferred from tracker metadata (an issue's author or creation time does not prove this session made it).
4. Resolve it with its type's engine in **`references/ticket-engines.md`**, **zoom as needed**: fetch the full body of related/closed tickets on demand. If in doubt on a `decision` ticket, run **Grilling** + **Domain vocabulary**. If in doubt on a `build`, **stop and ask** (see the Discuss step): never auto-grill.
5. Record the resolution: a comment with the answer (for a `decision`, carrying the `Builds:` line of its engine's **Carry it into build** step, with the build tickets created before the close), `gh issue close`, add the pointer to *Decisions so far* (default path: `node <templates>/trailhead-issue-body.js append --issue <map> --section "Decisions so far" --line "<pointer>"`, which does the fail-safe read-modify-write, the concurrent-edit checks and the secret scan in one call; when the helper is missing, do it by hand per the Fail-safe body rewrites rule in `../_shared/substrate.md`). Unblocking is a separate step that does not depend on the map or on `Decisions so far`, and it runs for **every** ticket, map or whiteboard, via `work` or `quick`: **unblock dependents** (default path: `node <templates>/trailhead-unblock.js --issue <n> --repo <owner/name>`; by hand when the helper is missing or errors, see the Resolve step in `references/ticket-engines.md`): for every ticket this one was blocking, if it was the last open blocker, remove its `trailhead:blocked` label so it graduates onto the frontier.
6. Add newly-surfaced tickets (create-then-wire, labelling blocked ones `trailhead:blocked`); graduate the fog that became specifiable: re-read **every entry** of *Not yet specified* (not just the ones this ticket names) against the answer, apply the **Ticket or fog** test of charting (`../trailhead-chart/references/charting.md`, Mode 1 step 4) to each, and propose all the entries that pass as **one batch** (title, type, blockers per ticket), created on a yes, so the user never has to dissolve ripe fog one entry per session. For each graduated entry, clear the patch from *Not yet specified* (default path: `node <templates>/trailhead-issue-body.js remove --issue <map> --section "Not yet specified" --match "<unique text of the entry>"`, which drops exactly one list entry (no match is a reported no-op, several matches abort); when the helper is missing, do the fail-safe read-modify-write by hand). If the answer reveals a ticket sits beyond the destination, apply the **out-of-scope vs deferred** test (`../_shared/out-of-scope.md`): if it's really deferred on something outside the map, ask the user to route it to a `seed`/`idea`/`todo`; only if it's truly beyond the destination rule it **out of scope** (label, then **drop its native sub-issue edge** to reclaim a slot, then close: the eager drop in `../_shared/substrate-commands.md`, 404-tolerant, references intact) instead of resolving it. If the decision invalidates other parts of the map, update or delete them.
7. **Hand off.** Close the session with the **Session handoff** ritual (`../_shared/session-handoff.md`): confirm the ticket is resolved by name, then the scannable **next-step block with `/clear` first** (never the next command without it), naming the next frontier ticket and giving its number (or, when several mutually independent tickets sit on the frontier, presenting them as a **set to choose from** rather than crowning the first-listed), or `/trailhead:map` if the frontier is empty/ambiguous. This holds in any later follow-up too, not only at the moment of resolution. **If this resolution leaves the map exhausted** (no open tickets and no fog left: the destination is reached), say so; but **before closing it out, if *Out of scope* holds any deferred line** (apply the out-of-scope-vs-deferred tells in `../_shared/out-of-scope.md`), **stop and ask the user what to do with each** (a `seed` gated on its trigger, an `idea`, or a `todo`, in this or another map), so nothing wanted-later is lost when the map goes quiet. Only once the deferred items are routed (or the user says leave them), **ask the user whether to close the map issue** now that the destination is reached: `gh issue close` it on a yes (it stays the record), leave it open on a no; never close a map unprompted. Either way **refresh the dashboard** so an exhausted/closed map drops off (a structural event): `node <templates>/trailhead-dashboard.js` regenerates the whole body from the live tracker in one call (`--dry-run` previews it); when the helper is missing, regenerate it by hand per `../_shared/substrate-commands.md`. The `clear` of the work state (`node <templates>/trailhead-work-state.js clear --repo <working-root>`) also removes the session-ticket marker, so the handoff needs just that one call. Maps aren't pinned, so there's no pin to free; the dashboard, codebase, and conventions stay pinned regardless.
## Work one ticket whole, without splitting: `quick`
A streamlined way to just get one ticket done, without map ceremony. `quick "<text>"` opens a **whiteboard** ticket (a `build`, or a `bug` when the text is clearly a defect) and works it end to end now; `quick <n>` works an **existing** ticket (whiteboard or map) the same way. For `quick <n>`, the **Preflight** above runs first: a closed, out-of-scope, superseded, or nonexistent ticket stops immediately, before config, claim, or isolation setup. Claim it first; still one ticket per session. **Bare `/trailhead:quick`** (no argument, e.g. offered in a Session handoff) asks for the piece to work, like a capture missing its text, then proceeds as `quick "<text>"`.
It runs the **full engine** (the `build`/`bug` cycle in `references/ticket-engines.md`: Discuss → Plan → Execute → Verify → Resolve), differing from `work` on these points:
- **Briefs and confirms scope first, exactly as Work the map step 3.** Whether the menu is posed at all follows **Skip the confirm on a ticket this session already shaped** (Work the map step 3). When it is, with the ticket claimed, summarise what resolving it will do and pose the same scope-confirm menu (`AskUserQuestion`: **Proceed** (Recommended), **Change something**, delegate; numbered list on Codex), applying the same consistency rule (a divergent request is offered as work-as-is vs a separate new ticket, as the same no-delegate menu, opened only if the user chooses it, never forced; via **Scope that surfaces while working a ticket**, `../_shared/surfaced-scope.md`), and, exactly as Work the map step 3, if the user defers that choice rather than settling it here, mark the divergent follow-up pending for the Pending follow-up guard (`../_shared/session-handoff.md`) so it is re-posed at the handoff. Whether the menu is skipped follows **Skip the confirm on a ticket this session already shaped**: a `quick "<text>"` ticket gets the one-line note, no menu; `quick <n>` follows the same rule as `work`.
- **Grill only if needed** (Discuss): never auto-grill; start from Plan and stop to ask only if a blocking ambiguity surfaces, exactly as the `build` Discuss (first run the routing step at the top of `../_shared/techniques/grilling.md`, then the rounds as **Who runs a round** there says). The user may ask to grill at any time.
- **Never splits.** If the ticket turns out large, work it whole anyway: that is the point of `quick`. Do not `split` it and do not spin children. (New scope that surfaces mid-work is still captured out, never folded in, see **Scope that surfaces while working a ticket** (`../_shared/surfaced-scope.md`).)
- **No map narrative book-keeping.** A whiteboard ticket has no map, so there is no `Decisions so far` update, no fog to graduate, no frontier re-scan. Everything else holds: atomic commits with `Refs: #<n>`, TDD / Code review / Acceptance testing per the cycle, the resolution comment + `gh issue close`, unblocking dependents (`trailhead-unblock.js`, map or whiteboard ticket alike), and the **Session handoff** (`../_shared/session-handoff.md`). Dependent-unblocking always runs (see **Work the map** step 5 above): skipping it would strand those dependents off the frontier for good. Only the *narrative* book-keeping above and the split are skipped, so prefer `work <n>` for a map ticket that has dependents or belongs in the map record.
- **Same isolation setup as `work`.** A claimed ticket that carries `trailhead:paused` drops it at the claim, exactly as in **Work the map** step 2. It clears `trailhead:needs-you` the same way (`node <templates>/trailhead-needs-you.js clear --issue <n> --repo <owner/name>`); a failed `clear` is reported to the user in one line but never blocks the claim. `quick` **writes the `.trailhead/session-ticket` marker at the working root in every isolation mode** (exactly as **Work the map** step 2, so tooling like the statusline shows what's in play; the active-map marker follows the same rule as `work`: a named ticket's map becomes the active map, a whiteboard ticket leaves it alone, see **Active map** in `../_shared/multi-map.md`). Under `auto` it runs the same work-start concurrency ask (with the same `check`/`record` answer reuse) **before any edit** and resolves to current-checkout / worktree / clone by the same rule; under an explicit `isolation: worktree`/`clone` it sets up the ticket's `trailhead/t<n>` workspace before any edit (including the submodule-forces-`clone` escalation and submodule provisioning) and re-checks the claim; under `isolation: none` (or `auto` answered no), the scope-free check applies too. Under `worktree`/`clone` it also runs the same gitignored-file copy (`trailhead-isolation-copy.js --from <main-checkout> --to <working-root>`) right after creating the workspace. `quick` also runs the same commit-msg hook sync at the working root, exactly as **Work the map** step 2. It also writes the work state last, exactly as **Work the map** step 2, with `--map none` for a whiteboard ticket.
- **Refreshes the dashboard at the handoff, not mid-work.** A `quick "<text>"` whiteboard ticket is born and resolved in the **same** session, so its dashboard refresh rides with the session's end, not with ticket creation: **do not refresh right after the ticket exists** (that lands a structural pinned-issue write ahead of the work `quick` exists to just-get-done, and only duplicates the resolution refresh a moment later). Refresh at **Resolve** (the handoff), as a **full regeneration from the live tracker** (`node <templates>/trailhead-dashboard.js`, never a partial patch that leaves other sections knowingly stale), so the whiteboard ticket lands on the dashboard as the session closes. **If the session pauses before Resolve** (`/trailhead:pause`), refresh there instead, so a deliberately-unfinished quick still shows (the read-only renders never rewrite the dashboard body, so nothing else would surface it). `quick <n>` on a *map* ticket does **not** refresh (map ticket progress shows via the native bar). See **The dashboard** and its freshness rule in `../_shared/substrate.md`.
- **Always size-triages, independent of `config.effort`.** `quick` is the home of the cost-aware path: reaching for `quick` is itself the signal the piece is small, so it runs the **same size triage and ask-to-skip offer defined canonically under effort in `../_shared/configuration-reference.md`** (the same one the `build`/`bug` engine runs, `references/ticket-engines.md`) **every time, at the start before planning, whatever `config.effort` is set to**. `quick` does not wait for `effort: lean`, and an explicit `effort: standard` does not suppress it: `effort` governs `work` / the map cycle, not `quick`. On a trivial/small ticket it surfaces the one consolidated offer (the plan pass, TDD, cross-AI review, the full code review, the goal-backward verify) and goes lean **only on an explicit yes**; on a large ticket there is no offer and the full cycle runs. The correctness spine (atomic `Refs:` commits, tests/criterion, post-commit self-check, Resolve) is never in the offer, whatever the size or the answer.
Type follows the ticket: `quick "<text>"` defaults to `build` (`bug` if a defect); `quick <n>` uses the existing type and its matching engine.
**Ensure the whiteboard label exists before applying it** (a repo that adopted trailhead before the whiteboard existed never created it): `gh label create trailhead:whiteboard --color C5DEF5 --description "Map-less ticket: lives on the whiteboard" 2>/dev/null || true`. See **The whiteboard** in `../_shared/substrate.md`.
## Pause, resume, split
- **`pause [note]`** / **`resume [ticket]`**: checkpoint the ticket in play so it can be resumed later, and pick it back up from its latest `PAUSED` checkpoint. Read **`../_shared/teamwork.md`** (Pausing & resuming) and follow it: the `PAUSED` checkpoint contents, release-vs-keep the claim, and (under `isolation: worktree`/`clone`) committing the work-in-progress in the ticket's isolated workspace and recording its path in the checkpoint, then re-entering it on resume.
- **`split [ticket]`**: split the named (or in-play) ticket into children that replace it. Read **`../_shared/teamwork.md`** (Splitting a ticket) and follow it: create the children (each wired to the map and to the split origin), re-point the original's blockers onto the right children, then supersede & close the original (`trailhead:superseded`).