| name | drupal-issue-review |
| description | Full workflow for reviewing/reproducing a drupal.org issue. Invoke with `/drupal-issue-review <issue-url-or-number>`. Reads the issue, determines required Drupal core + module versions, scaffolds a fresh environment, reproduces the bug, and optionally drafts a comment. Companion to drupal-contribute-fix (which handles the actual fix + MR packaging).
|
| license | GPL-2.0-or-later |
| metadata | {"author":"ajv009","version":"1.0.0"} |
drupal-issue-review
End-to-end workflow: read a drupal.org issue, set up a matching environment, reproduce, and comment.
Invoke: /drupal-issue-review <issue-url-or-number>
IRON LAW: NO ENVIRONMENT SETUP WITHOUT CLEAR REPRODUCTION STEPS. If the issue doesn't have clear steps, read comments until you find them or ask the user.
IRON LAW (VERIFICATION): NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE. Don't say "reproduced" without showing the error. Don't say "verified" without test output.
Classification Sentinel Check (MANDATORY first action)
Before doing any review work, read the classification sentinel:
CLASSIFICATION_FILE="$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}/workflow/00-classification.json"
if [ -f "$CLASSIFICATION_FILE" ]; then
cat "$CLASSIFICATION_FILE"
fi
Expected shape: see docs/workflow-state-files.md for the full schema.
Branching:
- File does NOT exist: this session was started without the launcher
(e.g.,
/drupal-issue-review invoked directly without the full chain).
Treat this as a fresh run and fall through to normal review. No
reinstate.
status == "classified" (or any other non-PENDING terminal state):
proceed with review normally.
status == "PENDING": the launcher wrote the sentinel but
/drupal-issue did not overwrite it with real classification data.
REINSTATE:
- Invoke the Skill tool:
/drupal-issue with the issue id
- Wait for it to return
- Re-read the sentinel
- If the status is STILL
"PENDING" after the reinstate returned,
this is a FATAL condition. Present the escalation message below
and STOP. Do NOT attempt a second reinstate.
- Otherwise, continue with review.
Single retry only. No counter in the sentinel. Rationale: if
/drupal-issue cannot classify on retry, the problem is structural
(skill broken, fetcher failed, user interrupted) and looping won't fix
it. The session JSONL will show two /drupal-issue invocations in
sequence, making post-mortem easy.
Reinstate escalation message (only if single retry fails)
REINSTATE FAILED — issue #{issue_id}
/drupal-issue-review called /drupal-issue to reinstate classification,
but DRUPAL_ISSUES/{issue_id}/workflow/00-classification.json still shows
status: PENDING after the reinstate returned.
This indicates /drupal-issue itself is not completing Step 2.5.
Please investigate manually:
1. Check DRUPAL_ISSUES/{issue_id}/artifacts/ — did fetching complete?
2. Run /drupal-issue {issue_id} directly and watch for errors at Step 2
3. If /drupal-issue runs cleanly when invoked manually, the auto-chain
may have a model-level flake; re-run the launcher
Stopping review until you resolve this.
Rationalization Prevention (sentinel check)
| Thought | Reality |
|---|
| "The sentinel is probably fine, I'll just trust it" | Read it. PENDING is the common failure mode this check exists to catch. |
| "I already read the classification in artifacts, I don't need the sentinel" | artifacts/issue.json is the raw Drupal API data; the sentinel is the model's committed classification decision. They are different things. |
| "/drupal-issue already ran before me in the chain, it must have classified" | That's exactly the assumption that failed 5 times in the audit that motivated ticket 031. |
Hands-Free Operation
Hands-free from DDEV setup through reproduction. Canonical rules in
CLAUDE.md "Hands-Free Workflow (Critical)". After reproduction/testing,
auto-chain:
- Issues found needing code fixes ->
/drupal-contribute-fix
- MR verified and working ->
/drupal-issue-comment (confirming comment)
- Bug reproduced, no fix from us ->
/drupal-issue-comment (findings)
Stop point is the push gate in /drupal-contribute-fix or the draft comment.
Before You Begin
Before scaffolding a DDEV environment, answer these questions internally:
- What Drupal core version does this issue target?
- What module version/branch is needed?
- Are there specific contrib modules that need to be installed?
- Are there existing MRs I should apply to test with/without the fix?
- Does the issue have concrete reproduction steps, or do I need to derive them?
- Is there already a DDEV environment for this issue number in DRUPAL_ISSUES/?
If answers are unclear, go re-read the issue. Do NOT start ddev config until you can answer all 6.
Using Fetched Artifacts
If DRUPAL_ISSUES/{issue_id}/artifacts/ exists (populated by drupal-issue-fetcher), read:
issue.json for version, project, component, status
merge-requests.json for MR branches, primary MR, fork details
comments.json for reproduction steps (search comment bodies for "steps to reproduce" or numbered lists)
This replaces the need to re-read the issue from the browser. The answers to the "Before You Begin" questions should come from these artifacts.
Gotchas
- Never stop/delete other DDEV projects. Only operate within
DRUPAL_ISSUES/{id}/. Other instances in DRUPAL_ISSUES/ may be in
active use. On port conflict, ask the user — do not auto-increment or
stop neighbors.
- MR freshness: dry-run
git apply --check before code review. A
green GitLab pipeline on the MR's own base branch does NOT imply clean
apply to the current target branch. Stale MRs waste review effort.
- Drush login links are single-use and expire quickly. Regenerate
ddev drush uli --no-browser for each agent-browser session; a saved
URL from 10 minutes ago will fail silently.
- Dependency declaration gaps. Some modules need
drupal/token,
drupal/key, etc. without declaring them. If drush en fails with a
missing dependency, composer require it and retry; don't assume the
issue.
- Discover project test base classes before writing tests. The AI
module provides
BaseClassFunctionalJavascriptTests with built-in
takeScreenshot() and video recording. Use project base classes when
they exist instead of building from KernelTestBase/WebDriverTestBase
directly. Scan tests/ for *TestBase*, Base*Test*.
- Frontend diff changes require visual verification. CSS/Twig/JS/theme
diffs cannot be verified by reading code alone. A single class rename
can break icon positioning. Always take desktop + mobile screenshots
when the diff touches these files.
- Source awareness (dual issue sources).
issue.json carries a source
field: "do" or "gitlab". The metadata fields you extract
(status{code,label}, version, component, priority) are identical across
both sources, so the version-detection and extraction tables below apply
unchanged. The issue URL comes from issue.json["url"] (for gitlab this
is the GitLab work-item). MR/fork setup is unchanged (always GitLab).
Step 1: Read the Issue
Accept either format:
https://www.drupal.org/project/{project}/issues/{id}
- Just the number:
3561693
Normalize to the full URL. Read the issue via the fetched artifacts in
DRUPAL_ISSUES/{issue_id}/artifacts/ (populated by the drupal-issue-fetcher
agent — dispatch it with mode=full for a fresh fetch or mode=comments /
mode=delta --since <ts> for mid-work polls). If you need visual inspection
for embedded screenshots, use agent-browser. Read carefully — don't skim.
Pay attention to:
What to extract
| Field | Where to find it | Example |
|---|
| Issue ID | URL | 3561693 |
| Project | Breadcrumb / sidebar | ai |
| Version | Sidebar "Version" field | 1.2.x-dev |
| Component | Sidebar | AI Automators |
| Status | Sidebar | Needs work |
| Modules to enable | Steps to reproduce | ai_automators, canvas |
| Module versions | Comments, description | drupal/ai:1.2.x-dev, drupal/canvas |
| Drupal core version | Usually implied by module version; check comments | ^11 |
| Steps to reproduce | Issue body or comments | numbered list |
| Existing MRs | Issue fork section | !1288 on 1.2.x |
| Related issues | Comments, linked issues | [#3558728] |
Reading comments
Don't just read the issue body. Read every comment, especially:
- The latest comments (why was it reopened? what failed?)
- Any comments with screenshots or videos (view them)
- MR descriptions and code diffs
- Status changes (who moved it to Needs work and why?)
Version detection heuristics
If the issue doesn't explicitly state versions:
- Check the Version field in the sidebar
- Look at
composer require commands in comments
- Check MR target branches (
1.2.x, 2.0.x)
- If ambiguous, use the version from the sidebar
For Drupal core:
- Module requires
drupal/core:^11 → use drupal/recommended-project:^11
- Module requires
drupal/core:^10 → use drupal/recommended-project:^10
- Check the module's
composer.json if unsure
Step 2: Scaffold the ddev Environment
IMPORTANT: Do NOT stop, delete, or tear down any other DDEV projects. Other
instances in DRUPAL_ISSUES/ may be in active use. Only operate within the new
issue's directory. If there's a port conflict, ask the user.
ISSUE_DIR="$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}"
mkdir -p "$ISSUE_DIR"
cd "$ISSUE_DIR"
Environment Setup via Agent
Dispatch the drupal-ddev-setup agent with:
- Issue ID: from artifacts or the issue URL
- Project: from
issue.json field project
- Version: from
issue.json field version
- Mode:
fork if we need to fix code, packagist if just reproducing
- Fork details: from
merge-requests.json primary MR's source_branch (if fork mode)
Dispatch the DDEV agent with run_in_background: true so you can do useful
work while it sets up (~3-4 minutes).
Parallel Work While DDEV Sets Up
While the DDEV agent runs in the background (~3-4 min), read the MR diff
at artifacts/mr-{iid}-diff.patch and work through the static review
checklist at references/static-review-checklist.md. It covers coding
standards, test-coverage gaps, premise/architectural verification, and
MR freshness. Record findings to hand to the verifier/reviewer agents
after DDEV is ready. No running environment needed.
When the DDEV agent completes (you will be notified), use its enriched report
directly: it includes exact paths to phpunit/phpcs, module path, MR application
status, and DDEV URL. Do NOT re-verify file existence or binary paths.
If FAILED, review the error and either fix manually or ask the user.
The manual setup steps below are the FALLBACK if the agent is unavailable or fails.
Manual fallback (if the agent fails)
ddev config --project-type=drupal --project-name=d{issue_id}, then
ddev start, ddev composer create drupal/recommended-project:{core_version},
ddev composer require drush/drush, and
ddev drush site:install --account-name=admin --account-pass=admin -y.
Core version (^11, ^10) comes from Step 1. Prefer the agent over this.
Step 3: Install Required Modules
Install each module at the version specified in the issue:
ddev composer require drupal/{module1}:{version1} drupal/{module2} --no-interaction
ddev drush en {module1} {module2} {module3} -y
Watch for dependency errors. Some modules need drupal/token, drupal/key,
etc. If drush en fails with a missing dependency, composer require it and retry.
If the issue has an MR to test
To test WITH the fix applied:
cd web/modules/contrib/{module}
./scripts/fetch-issue --mode mr-diff --issue {issue_id} --mr-iid {mr_id} --out /tmp/mr-{mr_id}.diff --gitlab-token-file git.drupalcode.org.key
git apply --check /tmp/mr-{mr_id}.diff
If --check fails, the MR does not apply cleanly to the installed module version.
Do NOT proceed with review. Instead:
- Note the specific files/hunks that conflict
- Check
git log on the module to identify what changed upstream since the MR
- Draft a comment (via
/drupal-issue-comment) noting the MR needs a rebase,
listing the specific conflicts
- Do NOT mark RTBC on a stale MR
If --check passes:
git apply /tmp/mr-{mr_id}.diff
To test WITHOUT the fix (reproduce the original bug), just use the module as-is.
Step 4: Reproduce the Bug
Follow the issue's steps to reproduce exactly. If steps involve UI configuration:
- Generate a login link:
ddev drush uli --no-browser
- Use
agent-browser to navigate and interact (invoke the /agent-browser skill for detailed usage)
- Take screenshots at each significant step
Screenshot capture
Use the agent-browser skill for all browser interaction (full command
reference lives there). Typical flow: login via
ddev drush uli --no-browser, open in agent-browser, wait networkidle,
snapshot for refs, click/fill, screenshot --full to
$ISSUE_DIR/screenshots/NN-slug.png, close.
Save screenshots to $ISSUE_DIR/screenshots/ with numbered prefixes
(01-form-display.png, 02-config-setup.png, 03-error-triggered.png,
04-watchdog-log.png, etc.).
Visual verification when reviewing an existing MR with frontend changes
When reviewing an existing MR (categories B, E, I) whose diff includes .css,
.twig, .theme, or .js files, visual verification is part of reproduction/testing,
not a separate gate. No extra stop point is added.
- After applying the MR, take screenshots of all affected pages at desktop (1280px)
and mobile (375px via
agent-browser set viewport 375 812)
- Verify: icons render, positioning is correct, no overlapping elements, hover/focus
states work, responsive layout is intact
- If visual issues are found, they count as "issues found that need code fixes" in the
auto-continue decision (Step 5), which routes to
/drupal-contribute-fix
- If visual verification passes, include screenshot paths in the comment draft
Do NOT skip this for "simple CSS changes." The issue that prompted this rule was a
single class rename that broke icon positioning and missed an entire component
(Tools Library) that also needed updating.
Check error logs
After reproducing, always check:
ddev drush watchdog:show --count=5 --severity=3 --format=json
Compare the error message with what the issue reports.
Step 4.5: Pre-Work Gate (optional)
If --pre-work-gate was passed through from /drupal-issue, STOP here and present
a summary before auto-continuing. This gate only applies when the next step would
be writing a code fix (categories A, B with issues, C, E with scope escalation, G).
Skip it for comment-only outcomes (MR verified, cannot reproduce, just reply).
What to present
===========================================================
PRE-WORK GATE - Issue #{issue_id}
===========================================================
Classification: {letter} ({description})
Module: {module_name} ({version})
DDEV: {running_url or "not needed"}
Reproduction: {CONFIRMED / NOT REPRODUCED / N/A}
- {one-line summary of what was found}
Static Review Findings:
- {bullet points from parallel code review, if any}
Existing MRs: {summary of active MRs or "None"}
Preflight: {upstream fix status}
Suggested Actions:
1. [PROCEED] Write fix + kernel test (recommended)
2. [COMMENT] Just post findings, no code fix
3. [ADJUST] Change approach — describe what you want
4. [ABORT] Stop here
What would you like to do?
===========================================================
Wait for the user's response, then:
- PROCEED: Continue to
/drupal-contribute-fix as normal
- COMMENT: Invoke
/drupal-issue-comment with the findings
- ADJUST: Read the user's freeform text, treat it as the new approach directive,
then continue to
/drupal-contribute-fix with that guidance
- ABORT: Stop. Do not invoke any further skills.
If --pre-work-gate was NOT passed, skip this step entirely and auto-continue.
Step 4.9: Emit depth signals for solution-depth gate (MANDATORY)
Before auto-continuing to the next phase, write two files that the solution-depth
gate (/drupal-contribute-fix Step 0.5) will read. These files externalize what
you learned during review so the fresh pre-fix subagent has all context.
4.9a — workflow/01-review-summary.json
Structured summary of the review outcome. Write with heredoc:
mkdir -p "$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}/workflow"
cat > "$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}/workflow/01-review-summary.json" <<'JSON'
{
"issue_id": {issue_id},
"category": "{A-J letter from classification}",
"module": "{module machine name}",
"module_version": "{version from artifacts}",
"reproduction_confirmed": true,
"existing_mr": {"iid": {mr_iid_or_null}, "source_branch": "...", "apply_clean": true},
"static_review_findings": [
{"file": "src/Path.php", "concern": "brief description"}
]
}
JSON
Fill in the actual values from what you gathered during review. If a field is
not applicable, use null (not empty string).
4.9b — workflow/01a-depth-signals.json
Raw context for the pre-fix gate to reason about. IMPORTANT: this file contains
RAW text (reviewer narrative, maintainer comments) that the gate reads and
interprets. Do NOT attempt to parse or filter the text — pass it through.
cat > "$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}/workflow/01a-depth-signals.json" <<JSON
{
"category": "{A-J}",
"resonance_bucket": "{NONE|RELATED_TO|SCOPE_EXPANSION_CANDIDATE|DUPLICATE_OF}",
"resonance_report_path": "DRUPAL_ISSUES/{issue_id}/workflow/00-resonance.md",
"reviewer_narrative": $(jq -Rs . <<< "$(cat "$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}/workflow/static-review-notes.md" 2>/dev/null || echo 'No static review notes captured')"),
"recent_maintainer_comments": $(jq '[.[-5:] | .[] | {author, date: .created, body}]' "$CLAUDE_PROJECT_DIR/DRUPAL_ISSUES/{issue_id}/artifacts/comments.json" 2>/dev/null || echo '[]'),
"proposed_approach_sketch": $(jq -Rs . <<< "{brief sketch of the review's proposed fix plan, or 'none' if comment-only outcome}")
}
JSON
Where do the inputs come from?
category: from your classification in Step 4 (A-J)
resonance_bucket: from DRUPAL_ISSUES/{issue_id}/workflow/00-resonance.json if it exists; otherwise "NONE"
reviewer_narrative: from your parallel static review in "### Parallel Work While DDEV Sets Up" — save it to workflow/static-review-notes.md during that step and re-read it here
recent_maintainer_comments: the last 5 entries from artifacts/comments.json (jq handles this)
proposed_approach_sketch: your own 1-3 sentence sketch of what the fix should look like (for the gate to compare against)
Do NOT include criticism_keywords_hit or rationalization_matches — those
are reasoning tasks for the gate's opus model, not mechanical regex matches
from here.
bd write (best-effort): Mirror the review summary to bd:
BD_ID=$(scripts/bd-helpers.sh ensure-issue "$ISSUE_ID" "Drupal issue $ISSUE_ID" 2>/dev/null)
if [[ -n "$BD_ID" ]] && [[ -f "$WORKFLOW_DIR/01-review-summary.json" ]]; then
scripts/bd-helpers.sh phase-review "$BD_ID" "$WORKFLOW_DIR/01-review-summary.json"
fi
This step is MANDATORY for issues that will chain to /drupal-contribute-fix.
Skip for outcomes that do not chain to a fix (MR verified, cannot reproduce,
comment-only) — those don't need the depth gate.
Step 5: Auto-Continue to Next Phase
After reproduction/testing is complete (and after the pre-work gate, if enabled),
automatically continue. Do NOT ask the user (unless the pre-work gate is active).
| Outcome | Auto-Action |
|---|
| Issues found that need code fixes | Run preflight search, then invoke /drupal-contribute-fix |
| MR verified, working correctly | Invoke /drupal-issue-comment to draft confirming comment |
| Bug reproduced, no fix expected from us | Invoke /drupal-issue-comment to draft findings |
| Cannot reproduce | Invoke /drupal-issue-comment to draft "could not reproduce" comment |
Preflight Gate (MANDATORY before writing fixes)
If you identified issues during review that need code changes, you MUST run a
preflight search BEFORE writing any code. This checks if someone else already
identified and fixed the same problems:
DCF_ROOT=$(find ~/.claude -path "*/drupal-contribute-fix/SKILL.md" -exec dirname {} \; 2>/dev/null | head -1)
python3 "$DCF_ROOT/scripts/contribute_fix.py" preflight \
--project {module} \
--keywords "{brief description of issues found}" \
--out .drupal-contribute-fix
- If upstream fix exists: note it, do not duplicate the work
- If nothing found: proceed to
/drupal-contribute-fix
This takes 30 seconds and prevents duplicate MRs.
Output (if comment drafted) goes to: $ISSUE_DIR/issue-comment-{issue_id}.html
Companion Skills (Auto-Invoked)
This skill handles environment + reproduction. The next step is invoked
automatically based on the reproduction outcome; do not ask the user which.
| After this phase | Auto-invoke | Purpose |
|---|
| Issues found, need a fix | /drupal-contribute-fix | Write the fix + tests (chains reviewer/verifier agents, stops at push gate) |
| MR verified, no issues | /drupal-issue-comment | Draft confirming comment, present to user |
| Bug reproduced, no fix from us | /drupal-issue-comment | Draft findings comment, present to user |