# Investigation

> Create an investigation ticket + live-doc for sustained root-cause work (retros, bug archaeology, regression hunts).

- Skill: `me2resh/investigation` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add me2resh/investigation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/me2resh/investigation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: me2resh (https://skillmd.com/u/me2resh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/me2resh/investigation

---


# /investigation — Create an Investigation Ticket + Live-Doc

Read `.claude/rules/writing-standard.md`. Use the **controlled technical writing profile** for the
ticket and live document: state the outcome and next action first, then evidence.

Creates a structured GitHub Issue + a sibling live-doc markdown file for an **investigation** — sustained root-cause work whose deliverable is a *written artefact* of what was observed, what we concluded, and what's next. Distinct from `/spike` (forward-looking hypothesis with a budget) and `/bug` (immediate-fix) — see the comparison block at the top of `templates/tickets/investigation.md`.

> **When to use an investigation vs a bug vs a spike.** A `/bug` is filed when you already know what's broken and need to coordinate the fix. A `/spike` is filed when you want to test a forward-looking hypothesis ("will this approach work?") inside a time budget. An `/investigation` is filed when the *question itself* is the unknown — "why did this happen?", "what's actually going on with the metric drift?", "how does competitor X handle this?". The investigation produces a written record; the bug fix that may follow is a downstream artefact.

## Path resolution

Read the registry path via `portfolio_registry`, the per-project docs dir via `portfolio_projects_dir`, and the ideas backlog via `portfolio_ideas_backlog` — all from `.claude/hooks/_lib-portfolio-paths.sh`. Source the helper at the top of any bash block that touches those paths:

```bash
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
registry=$(portfolio_registry)
projects_dir=$(portfolio_projects_dir)
```

Defaults match today's single-fork layout (`./apexyard.projects.yaml`, `./projects`, `./projects/ideas-backlog.md`). Adopters in split-portfolio mode override the `portfolio.{registry, projects_dir, ideas_backlog}` keys in `.claude/project-config.json`. Don't hardcode literal `apexyard.projects.yaml` or `projects/` paths in bash blocks — the helper resolves whichever mode the adopter is in. See `docs/multi-project.md`.

## Usage

```
/investigation                              # asks for the trigger interactively, derives slug
/investigation order-api-spike-may-12       # pre-fills the slug; asks for the trigger detail
/investigation PD-4471                      # incident ID as slug
```

## Process

### 0. Write the active-issue-skill marker (REQUIRED — me2resh/apexyard#268)

Before any `gh issue create` (or other tracker CLI), write this skill's name to the active-issue-skill marker so `require-skill-for-issue-create.sh` lets the command through. At skill entry:

```bash
# Resolve the ops-fork root the SAME way the hooks do (_lib-ops-root.sh):
# anchor on the .apexyard-fork marker (split-portfolio v2 — onboarding.yaml
# lives in the sibling portfolio repo, NOT the ops fork), falling back to the
# onboarding.yaml + apexyard.projects.yaml pair (single-fork v1).
ops_root="$PWD"; r="$PWD"
while [ "$r" != / ]; do
  if [ -f "$r/.apexyard-fork" ] || { [ -f "$r/onboarding.yaml" ] && [ -f "$r/apexyard.projects.yaml" ]; }; then
    ops_root="$r"; break
  fi
  r=${r%/*}
done
mkdir -p "$ops_root/.claude/session"
echo "investigation" > "$ops_root/.claude/session/active-issue-skill"
```

Remove the marker on **every** exit path (success, early-exit, user cancel, error):

```bash
rm -f "$ops_root/.claude/session/active-issue-skill"
```

The `clear-issue-skill-marker.sh` SessionStart hook sweeps stale markers from killed sessions, but a clean exit should never leave one behind. See AgDR-0030.

### 1. Resolve the target project

Read `.claude/session/current-ticket` to determine the active project context. Then:

```bash
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
registry=$(portfolio_registry)
projects_dir=$(portfolio_projects_dir)
```

- If the active ticket is on a managed project's repo → use that project's name + repo.
- Else if exactly one project is registered → use it.
- Else if multiple projects are registered → ask:

  ```
  Which project is this investigation for?
  (Or 'framework' if it's about the apexyard framework itself.)
  ```

- If no projects are registered, ask for the repo in `owner/repo` format. The live-doc lands at `docs/investigations/<YYYY-MM-DD>-<slug>.md` in the ops fork root (the framework's own investigations live there).

### 2. Verify the prefix is on the whitelist

Read `.ticket.prefix_whitelist` from `.claude/project-config.*.json`. If `Investigation` (case-insensitive) is not in the list, warn and stop:

```
This fork's ticket schema doesn't include 'Investigation' as a valid prefix.
Either add it to .claude/project-config.json → .ticket.prefix_whitelist, or
file the ticket using whichever prefix the fork uses for root-cause work.
```

(The shipped default in `.claude/project-config.defaults.json` includes `Investigation`. This check exists for forks that have customised the whitelist — see apexyard#109.)

### 3. Parse or ask for the slug

Take the slug from `$ARGUMENTS` (kebab-cased). If empty, ask:

```
What's a short slug for this investigation? (3-5 words, kebab-case)
Example: "order-api-spike-may-12" or "PD-4471".
```

The slug becomes part of the live-doc filename: `<YYYY-MM-DD>-<slug>.md`. Today's date prefixes automatically (so the same slug can be reused for a follow-up investigation next month without colliding).

### 4. Gather details (one section at a time)

Ask conversationally — do NOT batch all questions. Wait for each answer before asking the next. Mirror the section structure of `templates/tickets/investigation.md` so the user sees their answers slot into the artefact directly.

**a) Trigger (required)**

```
What kicked this off? One paragraph.
Examples:
  - "Production incident PD-4471 at 14:03 UTC on 2026-05-12 — error rate
    on /api/orders spiked from 0.2% to 11% for 22 minutes."
  - "Customer report from #cs-tickets-3471 — exports >100k rows always fail."
  - "Reviewing why we picked Auth0 in 2024 vs Cognito today."

Include the link / incident ID / date if you have it.
```

**b) Hypothesis being tested (required)**

```
What did you think was happening BEFORE starting? List 2–4 hypotheses
you wanted to confirm or rule out. I'll format them as a checkbox tree.

Example:
  - Upstream dependency degradation
  - Internal queue backup
  - Recent deploy regressed something
```

Push back if the user offers only one hypothesis — investigations exist precisely because the answer isn't obvious; entertaining multiple causes upfront is the methodology. If the user genuinely has one hypothesis only, that's usually a sign they should file a `/bug` instead. Surface that gently:

```
Only one hypothesis? An investigation usually entertains 2–4 — that's
what distinguishes it from a /bug (where you already know what's broken).
Are you sure /bug isn't the right call here? If yes to investigation,
I'll proceed with the one hypothesis.
```

**c) Evidence sources you'll consult (required)**

```
Where will you gather evidence? List the sources.
Examples:
  - CloudWatch logs for /api/orders, 14:00–14:30 UTC
  - Payments provider status page
  - order_events table (SQL)
  - Last 4 deploys to OrderService
  - Staging replay of failed payloads

These become the "Method" section — the path a reader follows.
```

**d) Initial method sketch (optional)**

```
Any specific queries / commands / steps you've already planned?
(or press Enter to fill in as you go)
```

If the user provides specifics, they go into the Method section. If they skip, the Method section starts empty in the live-doc and the investigator fills it in as evidence comes in.

### 5. Show the formatted ticket + live-doc plan for confirmation

Resolve the investigation template via the portfolio helper so adopter overrides win when present:

```bash
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
template=$(portfolio_resolve_template tickets/investigation.md)   # → custom-templates/tickets/investigation.md if present, else templates/tickets/investigation.md
```

Single-fork adopters (no `portfolio` block) and adopters with no override fall straight through to `templates/tickets/investigation.md`. Adopters who want a customised investigation shape (e.g. Five Whys instead of Hypothesis Tree) drop their version at `<private_repo>/custom-templates/tickets/investigation.md`. See `templates/README.md` for the path-mirroring convention.

**Backward-compat fallback**: if `portfolio_resolve_template` returns empty (template file missing — partial adopter setup or pre-#281 layout where the file lived at `templates/investigation.md`), fall back to the inline heredoc body below (the live-doc bootstrap structure mirroring the resolved template's sections) and print a one-line WARN on stderr (`WARN: tickets/investigation.md template missing — using inline fallback`).

Read the resolved template and substitute the gathered inputs into the four sections that have inputs (Trigger, Hypothesis being tested, Method sketch, plus the placeholder Findings / Conclusion / Follow-up actions sections to fill in as the investigation progresses).

Display the full ticket + the live-doc path:

```
Here's what I'll create:

GitHub Issue:
  Title:  [Investigation] {slug}
  Labels: investigation
  Repo:   {owner/repo}
  Body:   (template-rendered — Trigger + Hypothesis + Method sketch
           pre-filled; Findings / Conclusion / Follow-up actions empty
           for now)

Live-doc:
  Path:   {projects_dir}/{project}/investigations/{YYYY-MM-DD}-{slug}.md
          (or docs/investigations/{YYYY-MM-DD}-{slug}.md for framework
           investigations)

The live-doc IS the working surface — update it as evidence comes in.
The GitHub issue tracks visibility + close state (closes when every
Follow-up action lands or is explicitly dropped).

Create both? (yes / edit / cancel)
```

### 6. Handle response

- **yes** / **looks good** / **go** → create the issue + write the live-doc
- **edit X** / **change Y** → ask what to change, update, re-show
- **cancel** / **no** → abort

### 7. Write the live-doc

Compute the date prefix (UTC `YYYY-MM-DD`) and the target path:

```bash
date_prefix=$(date -u +%Y-%m-%d)
if [ "$project" = "framework" ]; then
  livedoc_dir="$(git rev-parse --show-toplevel)/docs/investigations"
else
  livedoc_dir="$projects_dir/$project/investigations"
fi
mkdir -p "$livedoc_dir"
livedoc_path="$livedoc_dir/${date_prefix}-${slug}.md"
```

Substitute the gathered values into the resolved template and write the file via the `Write` tool. The Metadata block at the bottom of the file references the GitHub issue number — leave it as `#{NNN}` until step 8, then patch it.

### 8. Create the issue (via the tracker abstraction)

Dispatch creation through `tracker_create` (#670 / AgDR-0072, extended by
the #709 creator sweep) so the investigation lands in **this project's** tracker —
GitHub, GitLab, or a `custom` CLI — per its `tracker:` block in
`apexyard.projects.yaml`. For a GitHub adopter this runs `gh issue create`
exactly as before.

```bash
# Resolve the tracker lib (it lives in the ops fork's hooks dir) by walking up
# from the cwd; source it.
tracker_lib="$(r="$PWD"; while [ -n "$r" ] && [ "$r" != / ]; do \
  [ -f "$r/.claude/hooks/_lib-tracker.sh" ] && { echo "$r/.claude/hooks/_lib-tracker.sh"; break; }; \
  r="${r%/*}"; done)"
# shellcheck source=/dev/null
. "$tracker_lib"

# Ensure the `investigation` trigger label exists on the target tracker.
# Best-effort (tracker_label_ensure always exits 0).
tracker_label_ensure "{owner/repo}" "investigation" "5319E7" "Sustained root-cause work — closes when Follow-up actions land, not on PR merge"

# Pass the body via a file (arbitrary markdown — never inline-interpolated).
body_file="$(mktemp)"
cat > "$body_file" <<'BODY'
{rendered template body, with the Metadata block pointing at the live-doc path}
BODY

# tracker_create <owner/repo> <title> <body_file> [<labels_csv>] → {"ref","url"}.
result="$(tracker_create "{owner/repo}" "[Investigation] {slug}" "$body_file" "investigation")"
rc=$?
rm -f "$body_file"
if [ "$rc" -eq 3 ]; then
  echo "Tracker is 'none' (shape-only) — nothing was created in a tracker." >&2
  echo "File this in your external system (e.g. Jira/Linear via MCP):" >&2
  printf '%s\n' "$result"
  exit 0
elif [ "$rc" -ne 0 ] || [ -z "$result" ]; then
  echo "Investigation creation failed — check the tracker CLI / auth. Nothing was created." >&2
  exit 1
fi

ref="$(printf '%s' "$result" | jq -r '.ref')"
url="$(printf '%s' "$result" | jq -r '.url')"
```

Use `$ref` to patch the live-doc's Metadata block — replace the `#{NNN}`
placeholder with the real reference.

### 9. Return paths + next steps

```
Created: {owner/repo}#${ref} — [Investigation] {slug}
${url}

Live-doc: {livedoc_path}

Next steps:
  1. Open the live-doc and update Findings as evidence comes in.
  2. Each hypothesis in the tree gets evidence-for / evidence-against
     bullets underneath — prune as you rule things out.
  3. When you reach a conclusion, fill in the Conclusion section.
  4. List Follow-up actions, each linked to a tracker ticket
     (`/bug`, `/feature`, `/spike`, `/decide`) or marked `(no follow-up)`.
  5. Close the GitHub issue when every Follow-up action is resolved
     — NOT on PR merge.

Suggested follow-up skills depending on what the investigation surfaces:
  /bug    — file an immediate-fix bug
  /spike  — file a hypothesis-driven exploration
  /decide — record a technical decision that fell out
  /feature — propose a new feature the investigation revealed a need for
```

## Rules

1. **One section at a time.** Never batch questions. Wait for each answer before asking the next.
2. **Always confirm before creating.** Show the full plan (issue + live-doc path) and get explicit "yes".
3. **At least 2 hypotheses by default.** If only one, gently surface that `/bug` might be the right tool. Allow the user to override.
4. **Trigger + Hypothesis + Evidence sources are mandatory.** Method sketch is optional (fills in as you go).
5. **Live-doc is the working surface.** The GitHub issue tracks visibility + close state; the live-doc holds the evolving evidence + findings.
6. **Close semantics are different from every other ticket type.** Investigations close when every Follow-up action is resolved or explicitly dropped — NOT on PR merge. The `investigation` label is the signal that downstream automation (if any) should treat the ticket as long-running.
7. **Labels.** `investigation` always. Priority labels (P0 / P1 / etc.) are NOT applied by default — investigations are scoped by *the question*, not prioritised by P-class. If the investigation is incident-driven and the operator wants a P-label, they can add one manually.
8. **No close gate (no `/investigation-close` skill).** The Follow-up actions section IS the close gate. Operators close the issue when actions land. Different from `/spike-close` because investigations have an open-ended action list, not a binary disposition. See AgDR-0027 for the rationale.
9. **Template override.** Adopters who prefer Five Whys / Fishbone over the default Hypothesis Tree drop a replacement at `<private_repo>/custom-templates/tickets/investigation.md`. The skill resolves via `portfolio_resolve_template tickets/investigation.md` — no skill changes needed.

## How investigations relate to other skills

| Skill | Purpose | When to chain into `/investigation` |
|-------|---------|-------------------------------------|
| `/bug` | Immediate-fix scenario, known broken behaviour | Rare — investigations usually file bugs as follow-up actions, not the other way around |
| `/spike` | Forward-looking hypothesis with a budget | An investigation may conclude "we need a spike to test the fix approach" — file via `/spike` as a follow-up action |
| `/debug` | Live debugging session (process helper) | A `/debug` session that surfaces a deeper "why did this happen" question naturally promotes to `/investigation` for the written artefact |
| `/decide` | Record a technical decision (AgDR) | An investigation often concludes with a decision — record it via `/decide` as a follow-up action; cite the investigation from the AgDR |
| `/feature` | Propose a new user-facing feature | An investigation that reveals a missing capability files a `/feature` as a follow-up action |
| `/migration` | Migration ticket + AgDR | If the investigation's remediation is a migration, file via `/migration` (which itself produces a ticket + AgDR pair) |

The bidirectional summary: investigations are usually **upstream** of other ticket types (they reveal what needs to happen); they're rarely downstream of them.

---

*Part of [ApexYard](https://github.com/me2resh/apexyard) — multi-project SDLC framework for Claude Code · MIT.*

