investigate

star 9.8k

Investigate bugs — single-issue deep root cause analysis or batch parallel investigation from a report or issue list

gitkraken By gitkraken schedule Updated 5/2/2026

name: investigate description: Investigate bugs — single-issue deep root cause analysis or batch parallel investigation from a report or issue list

/investigate - Bug Investigation

Perform structured root cause analysis of bugs. Operates in two modes: single-issue deep investigation or batch parallel investigation across multiple issues.

Pair with /live-exercise when the trace alone won't close the case

This skill is the static-tracing layer: read code, form a hypothesis, point at a likely cause. For many bugs that's enough — finish here. But if the bug is reproducible in a running instance AND any of the following apply, escalate to /live-exercise after the trace to validate the theory, add temporary logging and observe it, or iterate fix → re-verify:

  • Visible UI behavior: flaky rendering, intermittent visual glitches, hover/focus/animation issues, layout overflow, "user clicks X and sometimes Y"
  • Timing / event / race: focus shifts, microtask vs task ordering, async chains, pointerdown→focusout→click sequences, debounced/throttled handlers
  • Cross-component state: cause spans multiple modules, contexts, or shadow DOM boundaries — the trace keeps fanning out without converging
  • Theory needs empirical confirmation: "I think the cause is X, but I'd have to read 8 more files to be sure" → faster to run the theory than to keep tracing
  • Iterative debug loop expected: add logging → repro → read logs → adjust hypothesis → repro again. Single-shot static investigation can't do this efficiently
  • Bug surfaces only at runtime: state-dependent, dataset-dependent, configuration-dependent — code looks correct but behaves wrong in the live instance

Pattern: trace as far as the code alone takes you, identify the candidate cause(s), then hand off to /live-exercise to confirm or refute live. Don't skip the trace — but don't keep tracing past the point where running it would be cheaper.

Stay entirely here for: pure-logic bugs, parser/algorithm correctness, build/config errors, well-isolated functions where the trace is short, or when no running instance is available.


Usage

/investigate <symptom or issue reference>                         # Single deep investigation
/investigate <number> <number> [number...]                        # Batch parallel investigation
/investigate --from-report [path] [--verdict "..."] [--max 10]   # Batch from triage report
  • Single issue number or symptom — Deep investigation with full code tracing (runs in current agent)
  • Two or more issue numbers — Parallel investigation via subagents, produces report files
  • --from-report — Reads a triage decisions JSON. If path omitted, uses most recent *-DECISIONS.json in .work/triage/reports/.
  • --verdict — Filter report to specific verdict(s). Defaults to Valid - Needs Triage. Comma-separated. Only applies with --from-report.
  • --max — Maximum parallel investigations. Defaults to 10. Only applies to batch mode.

Mode Selection

Single mode — invoked with one issue number or a symptom description. Performs a deep, thorough investigation in the current agent context. Best for focused debugging.

Batch mode — invoked with 2+ issue numbers or --from-report. Spawns parallel subagents, each performing an independent investigation. Produces report files. Best for processing a queue of issues.


Single Mode Instructions

1. Understand the Symptom

  • Restate the symptom to confirm understanding
  • Identify: extension host or webview? Node.js or browser? Which feature area?
  • Ask clarifying questions if ambiguous

1a. Relevance Assessment (for issues older than 1 year)

If the issue is older than 1 year, perform a quick relevance check before the full investigation:

  1. Identify the feature area, code paths, UI elements, settings, or commands mentioned in the issue
  2. Check if the referenced files still exist using Glob or Grep
  3. If they exist, check git log --since="<issue creation date>" -- <relevant files> for significant changes
  4. If files have been deleted or substantially rewritten, include a Relevance Assessment in the output:
### Relevance Assessment

[One of:

- "Code path still exists — issue may still be relevant"
- "Code path no longer exists — [file(s)] deleted/removed since issue was filed"
- "Feature area significantly refactored — [summary of changes since issue creation]"
- "Unable to map issue to specific code paths — proceeding with investigation"]

If the code path no longer exists, note this prominently and consider whether the investigation should continue or if the issue should be recommended for closure.

2. Trace the Code Path

  • Find the entry point (command, event handler, IPC message)
  • Read every function in the call chain — do NOT assume behavior from names
  • For decorated methods, understand how the decorator alters behavior (see Decorator Reference below)
  • Read decorator source in src/system/decorators/ if behavior is unclear

3. Form Hypotheses

  • List at least 2 possible root causes
  • For each, identify what evidence would confirm or refute it
  • Gather evidence by reading code, checking error types, tracing data flow

4. Audit Impact

  • Search for ALL call sites of functions you plan to modify
  • Check both src/env/node/ and src/env/browser/ paths
  • Check sub-providers: src/env/node/git/sub-providers/ and src/git/sub-providers/

5. Assess Source Attribution

Before presenting findings, assess where the diagnosis came from:

  • Independent analysis — Root cause was determined primarily by tracing code paths, reading implementations, and forming hypotheses from code evidence. The issue description described symptoms but did not point to the cause.
  • Confirmed reporter's diagnosis — The issue already contained detailed code references, file paths, or a proposed root cause. The investigation verified these claims against the current code but did not independently discover the cause.

Be honest about this. Both are valuable — confirming a reporter's analysis is useful — but the reader should know what the investigation actually contributed.

6. Present Findings

## Investigation: [Symptom]

### Symptom

[What goes wrong]

### Source Attribution

[One of: "Independent analysis from code tracing" | "Confirms reporter's diagnosis — the issue included [specific detail: code references / file paths / root cause hypothesis] which this investigation verified against current code" | "Mixed — [explain what came from the issue vs. independent tracing]"]

### Code Path

[Entry point] -> [Function 1] -> [Function 2 (@gate)] -> [Function 3]

### Root Cause

[Cause with file:line evidence]

### Alternative Causes Considered

1. [Alternative] — ruled out because [evidence]

### Proposed Fix

[Minimal change to address root cause]

### Impact

- Files to modify: [list]
- Call sites checked: [count]
- Platform paths verified: Node.js [yes/no], Browser [yes/no]

7. Get Confirmation

Present findings and proposed fix. Wait for user confirmation before implementing.


Batch Mode Instructions

Stage 0 — Load and Filter Issues

Direct mode (2+ issue numbers):

Use the provided issue numbers directly. Proceed to Stage 1.

From-report mode (--from-report):

  1. Read the decisions JSON file specified (or find the most recent *-DECISIONS.json in .work/triage/reports/)
  2. Filter verdicts to only those matching the --verdict filter AND where the issue is a bug (check recommendedLabels or the corresponding markdown report for type info)
  3. If no matching issues are found, report that and stop
  4. If more issues match than --max, take the first N and note how many were skipped

Stage 1 — Fetch Issue Context

For each qualifying issue, use the GitHub CLI to fetch the full issue body and comments:

gh issue view <number> --repo <repo> --json title,body,comments,labels,state,author,createdAt,updatedAt

The repo slug comes from the decisions JSON's corresponding evidence pack, or default to gitkraken/vscode-gitlens.

Stage 2 — Spawn Investigation Subagents

For each issue, spawn a subagent (using the Agent tool) with:

  • subagent_type: general-purpose
  • A prompt that includes:
    1. The issue number, title, body, and comments (formatted for readability)
    2. The labels and any existing evidence summary from the triage verdict
    3. Instructions to follow the single-mode investigation methodology:
      • Understand the symptom from the issue description
      • Trace the relevant code path in the codebase
      • Form at least 2 hypotheses
      • Gather evidence by reading code
      • Assess source attribution: was the root cause found independently via code tracing, or was it confirming analysis already present in the issue?
      • Estimate effort (Small/Medium/Large) and risk (Low/Medium/High) based on the scope of the fix
      • Present findings in the investigation format
    4. A critical instruction: If there is not enough information in the issue to form a meaningful hypothesis, or if the investigation yields only low-confidence results, state that clearly and do not force a conclusion. It is perfectly acceptable to report "insufficient information to investigate" or "investigation inconclusive".
    5. The subagent should write its findings to stdout (not to files) — you will collect the results

Run subagents in parallel where possible. Each subagent operates independently.

Stage 3 — Collect and Report

Gather all subagent results and produce report files:

File: .work/triage/reports/YYYY-MM-DD-INVESTIGATION-REPORT.md

# Investigation Report — YYYY-MM-DD

Source: <decisions file path or "direct">
Issues investigated: N
Issues with findings: N
Issues inconclusive: N

---

## Findings

### [#NNNN — Title](https://github.com/<owner>/<repo>/issues/NNNN)

- **Author**: @username (team) | @username
- **Triage verdict**: <original verdict from triage, or "N/A" for direct mode>
- **Investigation result**: Confirmed Bug | Likely Fixed | Cannot Reproduce from Description | Inconclusive | Insufficient Information
- **Confidence**: High | Medium | Low
- **Source attribution**: Independent analysis | Confirms reporter's diagnosis | Mixed
- **Estimated effort**: Small (hours) | Medium (1-3 days) | Large (3+ days) | Unknown
- **Risk level**: Low | Medium | High | Unknown

#### Symptom

<restated from issue>

#### Code Path

<entry point> -> <function chain with file:line references>

#### Root Cause Analysis

<findings or "insufficient information to determine">

#### Alternative Causes Considered

1. <alternative> — ruled out because <evidence>

#### Recommendation

<what to do next — fix approach, request specific info from reporter, close, etc.>

---

## Inconclusive Issues

Issues where investigation could not reach a meaningful conclusion:

- [#NNNN — Title](https://github.com/<owner>/<repo>/issues/NNNN) — <reason: insufficient repro info | vague description | external dependency | etc.>

---

## Classification Matrix

Buckets confirmed/likely bugs by estimated effort and risk to aid prioritization:

| Issue | Effort | Risk | Summary                       |
| ----- | ------ | ---- | ----------------------------- |
| #NNNN | Small  | Low  | <one-line description of fix> |
| #NNNN | Medium | High | <one-line description of fix> |

**Effort guide**: Small = isolated change, hours of work; Medium = multiple files/systems, 1-3 days; Large = architectural or cross-cutting, 3+ days.
**Risk guide**: Low = safe, localized change; Medium = touches shared code or has edge cases; High = could regress other features or affects critical paths.

### Quick Wins (Small effort, Low/Medium risk)

- #NNNN — <title>

### Needs Planning (Medium/Large effort or High risk)

- #NNNN — <title> — <why it needs planning>

---

## Summary

- **Confirmed bugs**: N (list issue numbers)
- **Likely already fixed**: N (list issue numbers)
- **Inconclusive**: N (list issue numbers)
- **Skipped (over max)**: N

Write the markdown file and report its path to the user.

Machine-Readable JSON

Also produce a machine-readable companion file: .work/triage/reports/YYYY-MM-DD-INVESTIGATION-DECISIONS.json

{
    "reportId": "<uuid>",
    "sourceDecisionsFile": "<path to triage decisions that triggered this, or null for direct mode>",
    "generatedAt": "<ISO timestamp>",
    "investigations": [
        {
            "issueNumber": 1234,
            "result": "Confirmed Bug | Likely Fixed | Cannot Reproduce | Inconclusive | Insufficient Information",
            "confidence": "High | Medium | Low",
            "sourceAttribution": "Independent | Confirms Reporter | Mixed",
            "estimatedEffort": "Small | Medium | Large | Unknown",
            "riskLevel": "Low | Medium | High | Unknown",
            "rootCauseSummary": "...",
            "proposedFix": "...",
            "affectedFiles": ["src/path/to/file.ts"],
            "blockedBy": null | "vscode" | "git" | "cli" | "language-server" | "other",
            "blockedDetail": "...",
            "recommendation": "Fix | Request Info | Close | Needs Planning | Blocked"
        }
    ]
}

Generate a UUID for reportId. Write both files and confirm their paths to the user.


Important Notes

  • Batch mode is intentionally expensive — each subagent performs a full code investigation. The user has opted in to this cost.
  • Do NOT skip the investigation for an issue just because it seems complex. Let the subagent try and report what it finds.
  • DO skip issues that are clearly feature requests mislabeled as bugs — note these in the report.
  • Subagent failures (timeouts, errors) should be noted in the report, not silently dropped.
  • If a subagent finds that a bug has already been fixed (e.g., the code path no longer has the described behavior), report that as "Likely Fixed" — this is valuable triage signal.

Anti-Patterns

  • Do NOT start implementing before completing the investigation
  • Do NOT blame logging decorators for hangs — check @gate() first
  • Do NOT propose disabling/removing a feature when asked to fix it
  • Do NOT suppress errors — fix the root cause or propagate properly

Common Misdiagnosis Patterns to Avoid

  1. Blaming logging decorators for hangs: When a method hangs, the issue is almost never in @info()/@debug()/@trace(). Check @gate() (promise never resolving) or the actual async operation first.
  2. Confusing @gate() and @sequentialize(): @gate() returns the SAME promise to concurrent callers. @sequentialize() QUEUES calls. These solve different problems.
  3. Wrong error type handling: Use the error's .is() static method with reason discriminator: PushError.is(ex, 'noUpstream'), not instanceof + ex.message.includes(...).
  4. Platform-specific bugs: Something working in Node.js may fail in browser (and vice versa). Always check the @env/ abstraction layer.
  5. Scope/context bugs: getScopedLogger() returns stale scope after await in browser. Capture the scope before the first await.
  6. Suppressing errors instead of fixing them: Do NOT silence errors by catching and ignoring them. Fix the root cause or propagate them properly (e.g., use errors: throw so catch blocks handle them).

Chaining

This skill can be used standalone or as part of the issue workflow pipeline:

/triage recent → /investigate --from-report → /prioritize --from-report → /update-issues
/triage 5096   → /investigate 5096          → /prioritize 5096          → /update-issues
/investigate 5096 5084                        (standalone batch)
/investigate #5096                            (standalone single)

Upstream: --from-report consumes triage decisions JSON from /triage. Downstream: /prioritize --from-report consumes the investigation decisions JSON. /update-issues can also consume it directly.

Decorator Reference

Source files: src/system/decorators/

@info() / @debug() / @trace() — Logging (log.ts)

Same decorator at different log levels. Wraps methods to log entry/exit with timing and scope tracking.

Options (LogOptions):

  • argsfalse to suppress, or function for custom formatting
  • exittrue to log return value, or function for custom exit string
  • onlyExit — Suppress entry log; { after: N } only logs if duration > N ms
  • timingfalse to disable; { warnAfter: N } overrides slow threshold (default 500ms)
  • when — Conditional: skip logging entirely if returns false

Key behavior:

  • Wraps Promise results via .then() — does NOT await them
  • Slow calls (> 500ms default) log at WARN level
  • Creates ScopedLogger for async context tracking

getScopedLogger() constraints:

  • Must be called BEFORE any await in method body (browser uses counter-based fallback unreliable after await)
  • Must be inside a method decorated with @info()/@debug()/@trace()
  • ESLint rule @gitlens/scoped-logger-usage enforces both
  • Node.js uses AsyncLocalStorage for reliable cross-async scope

@gate() — Concurrent Call Deduplication (gate.ts)

Returns the SAME promise to concurrent callers. Only one invocation runs at a time per instance (or per grouping key).

Options (GateOptions):

  • timeout — Default: 300000ms (5 minutes)
  • rejectOnTimeout — Default: true. When false, retries instead of rejecting

Key behavior:

  • Stores pending promise on instance via $gate$methodName property
  • Clears gate via .finally() when promise settles
  • Deadlock detection: warns at 90s and 180s, sends telemetry (op/gate/deadlock)
  • On timeout: rejects with CancellationError (or retries if rejectOnTimeout: false)
  • Optional getGroupingKey creates independent gates per key
  • Synchronous returns pass through unaffected

Common hang pattern: If a gated method hangs, check what the promise is waiting for. Is there a circular dependency? Is a nested gated call waiting on the outer gate?

@memoize() — Result Caching (memoize.ts)

Caches return value permanently on instance via Object.defineProperty (non-writable, non-configurable).

Options (MemoizeOptions):

  • resolver — Custom cache key generator from arguments
  • version'providers' for version-keyed invalidation

Key behavior:

  • Cache stored as instance property: $memoize$methodName
  • Version-keyed: cache key prefixed with version counter; invalidateMemoized('providers') bumps counter causing cache miss
  • No TTL — cached for lifetime of instance
  • Caches Promises — a rejected Promise stays cached permanently
  • Checks Object.hasOwn(this, prop) before computing

@sequentialize() — Sequential Execution Queue (sequentialize.ts)

Queues async calls to execute one at a time (unlike @gate() which deduplicates).

Options (SequentializeOptions):

  • getDedupingKey — Consecutive calls with same key share result while waiting
  • getQueueKey — Creates independent parallel queues per key

Key behavior:

  • Chains calls via .then() — each waits for previous to complete
  • Without getQueueKey, all calls share single queue
  • With getQueueKey, different keys execute in parallel

Decorator Stacking

Execution order is bottom-up (outermost runs first):

@debug()     // Runs 1st: creates scope, logs entry/exit
@gate()      // Runs 2nd: deduplicates concurrent calls
async method() { ... }

Debugging priority:

  1. @gate() — hangs, timeouts, deadlocks
  2. @memoize() — stale data, cached rejections
  3. Logging decorators — rarely the cause
Install via CLI
npx skills add https://github.com/gitkraken/vscode-gitlens --skill investigate
Repository Details
star Stars 9,841
call_split Forks 1,736
navigation Branch main
article Path SKILL.md
More from Creator