From drupal-core
REQUIRED when user mentions a Drupal module + error/bug/issue - even without stack traces. Trigger on: (1) "<module_name> module has an error/bug/issue", (2) "Acquia/Pantheon/Platform.sh" + module problem, (3) any contrib module name (metatag, webform, mcp, paragraphs, etc.) + problem description. Searches drupal.org BEFORE you write code changes. NOT just for upstream contributions - use for ALL local fixes to contrib/core.
npx claudepluginhub ajv009/drupal-devkitThis skill uses the workspace's default tool permissions.
**Use this skill for ANY Drupal contrib/core bug - even "local fixes".**
lib/__init__.pylib/baseline_repo.pylib/drupalorg_api.pylib/drupalorg_urls.pylib/issue_matcher.pylib/issue_queue_integration.pylib/patch_packager.pylib/report_writer.pylib/security_detector.pylib/validator.pyreferences/core-testing.mdreferences/hack-patterns.mdreferences/issue-status-codes.mdreferences/patch-conventions.mdrequirements.txtscripts/contribute_fix.pyOrchestrates bug-fixing workflow: clarify symptoms, reproduce with failing tests, diagnose root cause, implement targeted fixes, verify, review, and document. Use for thorough bug investigations.
Learns open source project conventions from CONTRIBUTING.md, templates, and CODEOWNERS; implements matching changes; runs checks; generates PRs. Invoke /ccontribute {issue}.
Fixes GitHub issues using parallel analysis, hypothesis-based root cause analysis, similar issue detection, and prevention recommendations. Use for debugging errors, regressions, bugs, or triaging.
Share bugs, ideas, or general feedback.
Use this skill for ANY Drupal contrib/core bug - even "local fixes".
Checks drupal.org before you write code, so you don't duplicate existing fixes.
drupal-contribute-fix should focus on bug identification, triage quality, and report prep.
Use drupalorg-cli for issue-fork, MR, and pipeline execution steps.
Recommended split:
Quick prerequisite check:
drupalorg --version
php -v
Use drupalorg-cli commands (0.8+ expected, PHP 8.1+):
drupalorg issue:show <nid> --format=llm
drupalorg issue:get-fork <nid> --format=llm
drupalorg issue:setup-remote <nid>
drupalorg issue:checkout <nid> <branch>
drupalorg mr:list <nid> --format=llm
drupalorg mr:status <nid> <mr-iid> --format=llm
drupalorg mr:logs <nid> <mr-iid>
If drupalorg-cli is unavailable, fall back to the manual Drupal.org/GitLab flow below.
All script paths below are relative to this skill's root directory — NOT your current working directory. Before running any command, resolve the skill root once:
for d in "$HOME/.agents/skills/drupal-contribute-fix" "$HOME/.codex/skills/drupal-contribute-fix"; do [ -f "$d/SKILL.md" ] && DCF_ROOT="$d" && break; done
All commands below use $DCF_ROOT. You only need to run the line above once per session.
If you are debugging an error in docroot/modules/contrib/* or web/modules/contrib/*,
run preflight BEFORE editing any code - even if the user only asked for a local fix.
python3 "$DCF_ROOT/scripts/contribute_fix.py" preflight \
--project <module-name> \
--keywords "<error message>" \
--out .drupal-contribute-fix
preflight candidate matching is heuristic. Do not treat "already fixed" output as final without verification.
Before stopping work due to an "existing fix", you must verify all of the following:
If any verification step fails, treat it as a false positive and continue triage/fix flow.
This takes 30 seconds and may save hours of duplicate work.
Important: Drupal.org's api-d7 endpoint does not support a full-text text= filter (it returns HTTP 412). If you need a manual keyword search link, use the Drupal.org UI search:
https://www.drupal.org/project/issues/search/<project>?text=<keywords>
If the drupal-issue-queue skill is also available, use it for deeper triage and clean issue summaries (still read-only, still api-d7):
python scripts/dorg.py issue <nid-or-url> --format mdpython scripts/dorg.py search --project <machine_name> --status "needs review" --limit 20 --format json(Run those commands from the drupal-issue-queue skill directory.)
If the tool isn’t in a standard location, set DRUPAL_ISSUE_QUEUE_DIR=/path/to/drupal-issue-queue.
This skill should always end with a clear handoff package for upstream contribution.
If you made local code changes, run package:
python3 "$DCF_ROOT/scripts/contribute_fix.py" package \
--root /path/to/drupal/site \
--changed-path docroot/modules/contrib/<module-name> \
--keywords "<error message>" \
--test-steps "<step 1>" "<step 2>" "<step 3>" \
--out .drupal-contribute-fix
If you did triage-only (no local code change), preserve preflight evidence and provide:
drupalorg-cli commands to continue contribution workThen tell the user where the artifacts are and what to run next.
DO NOT delete these files:
.drupal-contribute-fix/ directorydiffs/ISSUE_COMMENT.mdREPORT.mdEven if the user asks to "reset" or "undo" the local fix, preserve the contribution artifacts so the fix can be submitted upstream. The whole point is to help the Drupal community.
1. DETECT → Error from contrib/core? Trigger activated.
2. PREFLIGHT → Search drupal.org BEFORE writing code
3. TRIAGE → Verify/score candidates, avoid false positives
4. PREP → Produce report-quality repro/test steps and recommendation
5. PACKAGE → If code changed, run `package`; otherwise keep preflight evidence only
6. HANDOFF → Prefer `drupalorg-cli` for fork/MR/pipeline execution
7. PRESERVE → Keep .drupal-contribute-fix/ artifacts for follow-up
Steps 4-7 are MANDATORY. Don't stop at "issue found"; leave an actionable handoff.
You don't need a stack trace to trigger this skill. Fire on high-level descriptions:
| User Says | Trigger? | Why |
|---|---|---|
| "The metatag module has an error" | YES | Module name + "error" |
| "mcp module isn't working with Acquia" | YES | Module + platform constraint |
| "I'm getting a bug in webform" | YES | Module name + "bug" |
| "paragraphs module throws an exception" | YES | Module + error indicator |
| "contrib module X has a problem" | YES | Explicit "contrib" mention |
| "my custom module has a bug" | Maybe | Only if it triggers a contrib/core bug |
Key insight: If the user mentions a Drupal module name (that isn't clearly custom) + any problem indicator (error, bug, issue, not working, exception, broken), trigger this skill FIRST. Don't wait until you've investigated and found a stack trace.
Error/exception originates FROM contrib or core code
modules/contrib/ or core/ as the sourceDrupal\<contrib_module>\ namespaceYou are about to edit files in contrib or core
docroot/modules/contrib/* or web/modules/contrib/*docroot/core/* or web/core/*docroot/themes/contrib/* or web/themes/contrib/*You are about to modify contrib/core code that should go upstream
Custom module encounters a bug in core/contrib
Hosting platform constraints cause contrib/core issues
Common misconception: This skill is only for patch uploads to drupal.org.
Reality: Use it for ALL local fixes to contrib modules. Why?
Look for these patterns in error messages or stack traces:
# Error ORIGINATES from contrib - USE THIS SKILL
Drupal\metatag\MetatagManager->build()
docroot/modules/contrib/mcp/src/Plugin/Mcp/General.php
web/modules/contrib/webform/src/...
core/lib/Drupal/Core/...
# Error in CUSTOM module - skill may not apply
# (unless the custom code is triggering a bug in contrib/core)
modules/custom/mymodule/src/...
web/core/, web/modules/contrib/, web/themes/contrib/docroot/core/, docroot/modules/contrib/, docroot/themes/contrib/patches/ directory (especially patches/drupal-*)preflight to search drupal.org (even for "local fixes")package to generate artifacts; otherwise keep preflight outputsdrupalorg-cli commands for fork/MR/pipeline flow.drupal-contribute-fix/ directory - NEVER delete it.diff filedrupalorg-cli for branch/MR/pipeline actionsNo new local diff artifact may be generated until upstream search + "already fixed?" checks are complete. No "STOP existing fix found" decision may be accepted until the file-level verification steps above are completed.
The skill ends in exactly one of these outcomes:
| Exit Code | Outcome | Meaning |
|---|---|---|
| 0 | PROCEED | MR artifacts + local diff generated |
| 10 | STOP | Existing upstream fix found (MR-based, historical patch attachments, or closed-fixed) |
| 20 | STOP | Fixed upstream in newer version (reserved for future use) |
| 30 | STOP | Analysis-only recommended (change would be hacky/broad) |
| 40 | ERROR | Couldn't determine project/baseline, network failure |
| 50 | STOP | Security-related issue detected (follow security team process) |
Workflow modes: When an existing fix is found (exit 10), the skill reports whether the issue has an active MR or only historical patch attachments to guide contributor workflow.
Drupal contributions should be handled through Merge Requests (MRs).
To reduce maintainer back-and-forth, this skill records workflow context but defaults to MR-only contributions for new work.
When available, drive execution with drupalorg-cli instead of manual UI/Git steps.
Outputs (in every issue directory):
WORKFLOW.md - at-a-glance workflow decision + links + MR-first guidanceREPORT.md - includes a Workflow section near the topISSUE_COMMENT.md - template is workflow-aware:
Rule of thumb:
Search drupal.org for existing issues without generating local artifacts:
python3 "$DCF_ROOT/scripts/contribute_fix.py" preflight \
--project metatag \
--keywords "TypeError MetatagManager::build" \
--paths "src/MetatagManager.php" \
--out .drupal-contribute-fix
After triage identifies the target issue/MR, use drupalorg-cli for issue-fork and MR execution:
drupalorg issue:show <nid> --format=llm
drupalorg issue:get-fork <nid> --format=llm
drupalorg issue:setup-remote <nid>
drupalorg issue:checkout <nid> <branch>
drupalorg mr:list <nid> --format=llm
drupalorg mr:status <nid> <mr-iid> --format=llm
drupalorg mr:logs <nid> <mr-iid>
Search upstream AND generate contribution artifacts if appropriate:
# For web/ docroot layout:
python3 "$DCF_ROOT/scripts/contribute_fix.py" package \
--root /path/to/drupal/site \
--changed-path web/modules/contrib/metatag \
--keywords "TypeError MetatagManager::build" \
--test-steps "Enable metatag" "Visit affected page" "Confirm fixed behavior" \
--out .drupal-contribute-fix
# For docroot/ layout (common in Acquia/BLT projects):
python3 "$DCF_ROOT/scripts/contribute_fix.py" package \
--root /path/to/drupal/site \
--changed-path docroot/modules/contrib/mcp \
--keywords "module not installed" "update_get_available" \
--test-steps "Set up failing config" "Trigger failing code path" "Confirm expected post-fix result" \
--out .drupal-contribute-fix
Note: package always runs preflight first and refuses to generate local artifacts
if an existing fix is found (unless --force is provided).
Generate a Tested-by/RTBC comment for an existing MR or diff artifact you've tested:
python3 "$DCF_ROOT/scripts/contribute_fix.py" test \
--issue 3345678 \
--tested-on "Drupal 10.2, PHP 8.2" \
--result pass \
--out .drupal-contribute-fix
Options: --result can be pass, fail, or partial. Use --mr or --patch
to specify which artifact you tested.
Legacy fallback only: reroll an existing patch attachment when maintainers explicitly request patch workflow:
python3 "$DCF_ROOT/scripts/contribute_fix.py" reroll \
--issue 3345678 \
--patch-url "https://www.drupal.org/files/issues/metatag-fix-3345678-15.patch" \
--target-ref 2.0.x \
--out .drupal-contribute-fix
This downloads the patch, attempts to apply it to your target branch, and generates a rerolled patch if needed (or confirms it applies cleanly). Prefer MR workflow for new contributions.
| Option | Description |
|---|---|
--project | Drupal project machine name (e.g., metatag, drupal) |
--keywords | Error message fragments or search terms (space-separated) |
--paths | Relevant file paths (space-separated) |
--out | Output directory for artifacts |
--offline | Use cached data only, don't hit API |
--force | Override gatekeeper and generate local diff artifact anyway |
--issue | Known issue number (runs gatekeeper check against this issue) |
--detect-deletions | Include deleted files in diff (risky with Composer trees) |
--test-steps | REQUIRED Specific test steps for the issue (agent must provide) |
Agents MUST provide specific test steps via --test-steps. Generic placeholders are not acceptable.
python3 "$DCF_ROOT/scripts/contribute_fix.py" package \
--changed-path docroot/modules/contrib/mcp \
--keywords "update module not installed" \
--test-steps \
"Enable MCP module with Update module disabled" \
"Call the general:status tool via MCP endpoint" \
"Before fix: Fatal error - undefined function update_get_available()" \
"After fix: JSON response with status unavailable" \
--out .drupal-contribute-fix
Test steps should:
.drupal-contribute-fix/
├── UPSTREAM_CANDIDATES.json # Search results cache (shared)
├── 3541839-fix-metatag-build/ # Known issue
│ ├── REPORT.md # Analysis & next steps
│ ├── ISSUE_COMMENT.md # Paste-ready drupal.org comment
│ └── diffs/
│ └── project-fix-3541839.diff
├── 3573571-component-context/ # Optional local CI evidence
│ ├── LOCAL_CI_PARITY_2026-02-16.md # Job/result summary + commands
│ └── ci/
│ ├── canvas-ci-local-full-20260216.log
│ └── canvas-ci-local-rerun-20260216.log
└── unfiled-update-module-check/ # New issue needed
├── REPORT.md
├── ISSUE_COMMENT.md
└── diffs/
└── project-fix-new.diff
Directory naming:
{issue_nid}-{slug}/ - Existing issue matched or specifiedunfiled-{slug}/ - No existing issue foundPreflight vs Package: preflight only updates UPSTREAM_CANDIDATES.json.
Issue directories are created by package when generating artifacts.
If local CI parity tooling is available (for example gitlab-ci-local), keep
evidence under the issue directory in .drupal-contribute-fix/:
LOCAL_CI_PARITY_YYYY-MM-DD.md: concise summary with exact commands, exit codes,
pass/fail/incomplete jobs, and blocker details.ci/*.log: raw logs for audit/debug follow-up.drupal-contribute-fix package now auto-creates LOCAL_CI_PARITY_YYYY-MM-DD.md
when ci/*.log exists in that issue directory and no parity summary file exists yet.Rules:
not run and why.If the fix appears security-related, the skill will STOP with exit code 50.
Security indicators:
Do NOT post security issues publicly. Follow the Drupal Security Team process: https://www.drupal.org/drupal-security-team/security-team-procedures
The skill enforces contribution best practices:
See references/hack-patterns.md for details.
The skill runs validation and reports results honestly:
php -l on changed PHP filesWhen triage/fix is complete, you MUST inform the user about the contribution artifacts and provide a CLI-first handoff:
I completed contrib/core bug triage and prepared contribution artifacts:
📁 .drupal-contribute-fix/<nid>-<slug>/
- REPORT.md - Triage findings and next steps
- ISSUE_COMMENT.md - Copy/paste issue or MR comment text
- WORKFLOW.md - MR/patch workflow recommendation
- diffs/<diff-file>.diff - Present only when code changed (local artifact)
**Recommended next commands (drupalorg-cli):**
drupalorg issue:show <nid> --format=llm
drupalorg issue:get-fork <nid> --format=llm
drupalorg issue:setup-remote <nid>
drupalorg issue:checkout <nid> <branch>
drupalorg mr:list <nid> --format=llm
For unfiled issues (no existing drupal.org issue found):
📁 .drupal-contribute-fix/unfiled-<slug>/
- Create a new issue at https://www.drupal.org/project/issues/<project> first
- Use ISSUE_COMMENT.md as the issue description template
- Then continue with drupalorg-cli using the new issue NID
DO NOT skip this step. The user may not know about the contribution workflow.
All Drupal core and contrib contributions use GitLab merge requests. Patch uploads are exception-only when explicitly requested by maintainers.
Reference: https://www.drupal.org/docs/develop/git/using-gitlab-to-contribute-to-drupal
drupalorg issue:show <nid> --format=llm
drupalorg issue:get-fork <nid> --format=llm
drupalorg issue:setup-remote <nid>
drupalorg issue:checkout <nid> <branch>
drupalorg mr:list <nid> --format=llm
drupalorg mr:status <nid> <mr-iid> --format=llm
drupalorg mr:logs <nid> <mr-iid>
git add <changed-files>
git commit -m "Issue #<nid> by <username>: <short description>"
git push
drupalorg mr:status <nid> <mr-iid> --format=llm
drupalorg mr:logs <nid> <mr-iid> # only if failing
git fetch origin
git checkout BASE_BRANCH_NAME
git pull
git checkout ISSUE_BRANCH_NAME
git rebase BASE_BRANCH_NAME
git push --force-with-lease
GitLab CI runs automatically on all merge requests. You cannot test patch files—contributions must be merge requests to be tested.
What runs automatically:
Interpreting results:
drupalorg mr:status <nid> <mr-iid> --format=llmdrupalorg mr:logs <nid> <mr-iid>Triggering test re-runs:
Important: GitLab CI uses phpunit.xml.dist, phpstan.neon.dist and other .dist files. Review these files as they may cause unexpected test failures.
If drupalorg-cli cannot run in the environment, use the issue page's Issue fork
controls and standard Git/GitLab UI as a fallback.
If local CI tooling exists in the contributor environment, run parity checks and
archive evidence under .drupal-contribute-fix/<issue>/:
ci/.LOCAL_CI_PARITY_YYYY-MM-DD.md (auto-scaffolded by
package when ci/*.log is present).If local parity tooling is not installed or blocked by environment constraints, state that clearly and do not claim full local parity.
Drupal core requires test coverage for all changes. Contrib modules don't require tests (though they're encouraged).
For core contributions, you MUST:
See references/core-testing.md for:
1. Detect + triage bug with `preflight`
2. Prepare report-quality reproduction and test steps
3. Identify target issue/MR (or file a new issue)
4. Use `drupalorg-cli` to set up fork remote + checkout branch
5. Make changes with test coverage (required for core)
6. Push commits and monitor MR pipeline
7. Iterate until pipeline is green and review feedback is addressed
| Task | Command |
|---|---|
| Show issue details | drupalorg issue:show <nid> --format=llm |
| Inspect fork + branches | drupalorg issue:get-fork <nid> --format=llm |
| Set up issue fork remote | drupalorg issue:setup-remote <nid> |
| Check out issue branch | drupalorg issue:checkout <nid> <branch> |
| List MRs | drupalorg mr:list <nid> --format=llm |
| Check MR pipeline | drupalorg mr:status <nid> <mr-iid> --format=llm |
| Read failing job logs | drupalorg mr:logs <nid> <mr-iid> |
| Push latest commit(s) | git push |
See examples/sample-report.md for a complete example.