
Typo3 Core Contributions
- 26 installs
- 1 repo stars
- Updated August 3, 2026
- netresearch/typo3-core-contributions-skill
Helps with ai & agent building tasks.
About
typo3-core-contributions is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- typo3-core-contributions
- AI & Agent Building
- AI-coding skill
Typo3 Core Contributions by the numbers
- 26 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #9,702 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/netresearch/typo3-core-contributions-skill --skill typo3-core-contributionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 26 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 3, 2026 |
| Repository | netresearch/typo3-core-contributions-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
TYPO3 Core Contributions
Guide for contributing to TYPO3 Core: Forge issues, Gerrit patches, CI debugging, and review workflows.
When to Use
- Forge issue analysis or creation (
forge.typo3.org/issues/*) - Patch submission, CI debugging, Gerrit review workflow
- Commit message formatting, cherry-picks, rebasing
Prerequisites
Run ${CLAUDE_SKILL_DIR}/scripts/verify-prerequisites.sh to check: TYPO3.org account, Gerrit SSH (ssh -p 29418 <user>@review.typo3.org), Git email matching Gerrit. See references/account-setup.md.
Workflow
1. Setup: Account + environment (${CLAUDE_SKILL_DIR}/scripts/setup-typo3-coredev.sh, references/ddev-setup-workflow.md) 2. Branch: git checkout -b feature/<issue>-description 3. Analyze: Understand root cause, reproduction steps, affected versions before coding 4. Develop: Implement fix + tests, validate with typo3-conformance-skill 5. Commit: Follow format below, include Resolves: #<issue> + Releases: 6. Push: git push origin HEAD:refs/for/main (starts as WIP) 7. CI: Wait for all jobs. Read actual GitLab logs at git.typo3.org/typo3/CI/cms/-/jobs/<id>. Fix ALL failures in one amend+push 8. Ready: Mark ready via git push origin HEAD:refs/for/main%ready or Gerrit UI "Start Review" 9. Review: Address feedback, amend commit, preserve Change-Id. Fetch reviewer's patchset first to preserve their message edits: git fetch origin refs/changes/XX/NNNNN/N && git reset --soft FETCH_HEAD 10. Update: git commit --amend && git push origin HEAD:refs/for/main
Commit Format
[TYPE] Subject (imperative mood, max 52 chars)
How and why (not what). Wrap at 72 chars.
Resolves: #12345
Releases: main, 13.4, 12.4Types: [BUGFIX], [FEATURE] (main only), [TASK], [DOCS], [SECURITY] Breaking: [!!!][TYPE] prefix, Releases: main only Required: Every commit MUST have Resolves: (not just Related:)
CI Debugging
Read ALL failing job logs (never guess). Common jobs: cgl pre-merge (code style), phpstan (static analysis), unit/functional (tests). Fix all in one patchset. Local checks:
./Build/Scripts/runTests.sh -s unit && ./Build/Scripts/runTests.sh -s functional
./Build/Scripts/cglFixMyCommit.sh
./Build/Scripts/runTests.sh -s phpstanKey Operations
| Task | Command |
|---|---|
| Push to Gerrit | git push origin HEAD:refs/for/main |
| Mark ready | git push origin HEAD:refs/for/main%ready |
| Set WIP | git push origin HEAD:refs/for/main%wip |
| Rebase | git fetch origin && git rebase origin/main |
| Cherry-pick patch | git fetch origin refs/changes/XX/NNNNN/N && git cherry-pick FETCH_HEAD |
| Install hook | cp Build/git-hooks/commit-msg .git/hooks/ && chmod +x .git/hooks/commit-msg |
| Fix email mismatch | GIT_COMMITTER_EMAIL="registered@email" git commit --amend --no-edit |
| Forge API | ${CLAUDE_SKILL_DIR}/scripts/create-forge-issue.sh, references/forge-api.md |
References
| Topic | File |
|---|---|
| Account setup | references/account-setup.md |
| Commit format | references/commit-message-format.md |
| Gerrit workflow | references/gerrit-workflow.md |
| Review patterns | references/gerrit-review-patterns.md |
| Modern patterns | references/modern-typo3-patterns.md |
| DDEV setup | references/ddev-setup-workflow.md |
| Forge API | references/forge-api.md |
| Commit hook | references/commit-msg-hook.md |
| Troubleshooting | references/troubleshooting.md |
TYPO3 Core Contribution Account Setup Guide
Complete guide for setting up all required accounts for TYPO3 Core contributions.
Overview
Three accounts are required for TYPO3 Core contribution: 1. TYPO3.org Account - Central authentication for all TYPO3 services 2. Gerrit Account with SSH - Code review and patch submission 3. Slack Access - Community communication and support
1. TYPO3.org Account
Registration
1. Visit the signup page: https://my.typo3.org/index.php?id=2
2. Fill in the registration form:
- Username: Choose alphanumeric identifier (avoid special characters like
@or!) - Email Address: Use email for Forge and Gerrit notifications
- Full Name: Use real name (community values genuine identification)
- Password: Create strong password (use password manager recommended)
3. Submit the form
4. Check your email for verification message
5. Click verification link to activate account
What This Account Provides
- Access to Forge issue tracker
- Authentication for Gerrit code review
- Access to my.typo3.org profile management
- Community member identification
Important Notes
- Username cannot be changed after registration
- Consider using personal email (not corporate) if contributing independently
- This account will be visible in git commits and Gerrit reviews
2. Gerrit Account Setup
Prerequisites
- Active TYPO3.org account
- SSH key pair (will create if needed)
Step 1: Sign In to Gerrit
1. Visit https://review.typo3.org 2. Click Sign In button (top right) 3. Authenticate with your TYPO3.org credentials 4. You'll be redirected back to Gerrit
Step 2: Generate SSH Key Pair
SSH keys are required for pushing patches to Gerrit.
Linux / macOS
# Generate SSH key pair
ssh-keygen -t ed25519 -C "your-email@example.org"
# Default location: ~/.ssh/id_ed25519
# Press Enter to accept default location
# Optionally set a passphrase for additional security
# View your public key
cat ~/.ssh/id_ed25519.pubWindows
Option A: Git Bash (Recommended)
# Same commands as Linux/macOS above
ssh-keygen -t ed25519 -C "your-email@example.org"Option B: PuTTYgen 1. Download and install PuTTY: https://www.putty.org/ 2. Run PuTTYgen 3. Click "Generate" and move mouse randomly 4. Save private key (*.ppk file) 5. Copy public key from text area
Step 3: Add Public Key to Gerrit
1. Click your profile icon (top right in Gerrit) 2. Select Settings from dropdown 3. Click SSH Keys in left sidebar 4. Paste your public key content (entire content of id_ed25519.pub or id_rsa.pub) 5. Click Add New SSH Key
Important: Only add the PUBLIC key, never the private key!
Step 4: Test SSH Connection
# Test connection to Gerrit
ssh -p 29418 <YOUR_USERNAME>@review.typo3.org
# Expected output:
# **** Welcome to Gerrit Code Review ****
# Hi <Your Name>, you have successfully connected over SSH.If you see the welcome message, SSH is configured correctly!
Multiple Devices
If you work on multiple computers:
Option 1: Copy Private Key
- Copy
~/.ssh/id_ed25519(private key) to other machines - Set proper permissions:
chmod 600 ~/.ssh/id_ed25519 - Not recommended for security reasons
Option 2: Generate Separate Keys (Recommended)
- Generate new key pair on each device
- Add all public keys to Gerrit settings
- Gerrit supports multiple SSH keys per account
- More secure: compromised device doesn't affect others
Troubleshooting
"Permission denied (publickey)"
- Verify key is added to Gerrit: Settings → SSH Keys
- Check key permissions:
chmod 600 ~/.ssh/id_ed25519 - Test with verbose:
ssh -vvv -p 29418 <username>@review.typo3.org
"Connection refused"
- Check firewall settings
- Verify port 29418 is accessible
- Try from different network
"Host key verification failed"
- Accept host key:
ssh-keyscan -p 29418 review.typo3.org >> ~/.ssh/known_hosts
3. TYPO3 Slack Workspace
Joining Slack
1. Visit https://typo3.slack.com
2. Click Create an account or Sign in
3. Use same email as TYPO3.org account (recommended for consistency)
4. Complete Slack registration
5. You'll receive invitation to TYPO3 workspace
Required Channels
#typo3-cms-coredev (Essential)
- Core development discussions
- Patch review requests
- Technical questions
- Get help from core team
Recommended Channels
#typo3-cms
- General TYPO3 CMS discussions
- User questions
- Extension development
#random
- Off-topic chat
- Community social
#announce
- Official announcements
- Release notifications
Using Slack Effectively
Asking for Reviews:
I've submitted a patch for issue #105737 (indexed search crash).
Would appreciate reviews: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345Asking Questions:
Working on #105737, need clarification on preg_replace error handling.
Should I use fallback or throw exception? Context: [brief explanation]Best Practices:
- Search before asking (knowledge base exists)
- Provide context and Forge/Gerrit links
- Be patient (volunteers respond when available)
- Use threads for discussions
- Thank people who help!
Slack Etiquette
- Don't @here or @channel unless critical
- Do use threads to keep discussions organized
- Don't DM core team members without asking first
- Do share knowledge when you can help others
- Don't expect immediate responses (volunteers have lives!)
Verification Checklist
Before proceeding with development, verify:
- [ ] TYPO3.org account created and email verified
- [ ] Can sign in to https://forge.typo3.org
- [ ] Can sign in to https://review.typo3.org
- [ ] SSH key added to Gerrit
- [ ] SSH connection to Gerrit successful:
ssh -p 29418 <user>@review.typo3.org - [ ] Joined TYPO3 Slack workspace
- [ ] Member of #typo3-cms-coredev channel
Run scripts/verify-prerequisites.sh to automatically check most of these!
Security Best Practices
SSH Key Security
- Never share private keys - TYPO3 team will never ask for them
- Use strong passphrase - Protects key if device is compromised
- Rotate keys periodically - Generate new keys annually
- Delete old keys - Remove unused keys from Gerrit settings
Account Security
- Use unique strong password - Use password manager
- Enable 2FA if available - Additional security layer
- Log out on shared devices - Don't stay signed in
- Review SSH keys regularly - Remove keys from old devices
Privacy Considerations
- Your name and email will be visible in:
- Git commit history
- Gerrit reviews
- Forge issue comments
- Consider using professional email if contributing as individual
- Company contributions may require corporate email
Next Steps
After completing account setup:
1. Proceed to Environment Setup (Phase 2 in main workflow) 2. Configure Git for TYPO3 contributions 3. Clone TYPO3 repository 4. Install Git hooks 5. Start contributing!
Support Resources
- Forge Account Issues: https://forge.typo3.org/projects/typo3cms-core
- Gerrit SSH Help: https://review.typo3.org/Documentation/user-upload.html
- Slack Support: Ask in #typo3-cms-coredev
- Documentation: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
Quick Reference
| Service | URL | Purpose |
|---|---|---|
| TYPO3.org Registration | https://my.typo3.org/index.php?id=2 | Create account |
| TYPO3.org Profile | https://my.typo3.org | Manage profile |
| Forge | https://forge.typo3.org | Issue tracking |
| Gerrit | https://review.typo3.org | Code review |
| Gerrit SSH Test | ssh -p 29418 <user>@review.typo3.org | Verify connection |
| Slack | https://typo3.slack.com | Community chat |
| Documentation | https://docs.typo3.org/m/typo3/guide-contributionworkflow/ | Full guide |
TYPO3 Commit Message Format Specification
Complete specification for TYPO3 Core contribution commit messages with examples and validation rules.
Structure Overview
[TYPE] Subject line (max 52 chars recommended, 72 absolute limit)
Detailed description explaining how and why the changes were made.
Lines wrapped at 72 characters. Explain the context and reasoning
behind the implementation approach.
Multiple paragraphs are allowed. Use bullet points with asterisks (*)
for lists:
* First item with detailed explanation
* Second item
* Third item
Resolves: #12345
Related: #12346
Releases: main, 13.4, 12.4
Change-Id: I1234567890abcdef1234567890abcdef12345678Subject Line
Format
[TYPE] Description starting with uppercase verb in imperative mood
Commit Types
| Type | Purpose | Branch Restrictions |
|---|---|---|
[BUGFIX] | Bug fixes | All branches |
[FEATURE] | New functionality | main branch only |
[TASK] | Refactoring, cleanup, misc | All branches |
[DOCS] | Documentation changes | All branches |
[SECURITY] | Security vulnerability fixes | All branches |
Breaking Changes
Use [!!!] prefix before type for breaking changes:
[!!!][FEATURE] Remove deprecated TypoScript syntax
[!!!][TASK] Drop support for PHP 7.4Important: Deprecations must NOT use [!!!] prefix!
Length Limits
- Recommended: 52 characters
- Absolute maximum: 72 characters
- Breaking changes get 5 extra chars:
[!!!]is not counted against limit
Imperative Mood
Subject must use imperative present tense (command form):
✅ Correct:
Fix memory leak in cache managerAdd support for WebP imagesRemove deprecated method callsUpdate documentation for hooks
❌ Wrong:
Fixed memory leak(past tense)Fixing memory leak(present continuous)Fixes memory leak(present tense)Memory leak fix(noun phrase)
Test: "If applied, this commit will _[your subject]_"
- "If applied, this commit will fix memory leak" ✅
- "If applied, this commit will fixed memory leak" ❌
Capitalization
- Start description with uppercase letter after
[TYPE] - No period at the end
✅ [BUGFIX] Fix null pointer exception in indexer ❌ [BUGFIX] fix null pointer exception in indexer ❌ [BUGFIX] Fix null pointer exception in indexer.
What to Describe
Describe what now works, not what was broken:
✅ Allow cancelling file exists modal ❌ Cancelling the file exists modal works now
✅ Limit element browser to default language pages ❌ Element Browser should only render default language pages
Description Body
Purpose
Explain the how and why of changes, not the what (code shows what).
Guidelines
- Wrap lines at 72 characters (URLs can be longer)
- Leave blank line after subject
- Explain context and reasoning
- Don't repeat Forge issue content
- Don't describe reproduction steps (belong in Forge)
- Do explain non-obvious implementation choices
- Do mention side effects or impacts
Bullet Points
Use asterisks (*) with hanging indents:
This change improves performance by:
* Caching compiled templates in memory
* Reducing database queries from N+1 to 1
* Pre-loading frequently accessed resourcesDocumenting Tests
When your patch includes tests, add a line to document this:
Added tests fixate this behavior.This standard phrase indicates that tests were added to prevent regression. Place it after the main description, before the "Affected" list (if any):
Example:
[BUGFIX] Fix crash on dynamic method calls
Several matchers crash when encountering dynamic method calls
like `$object->$methodName()`. This happens because the code
assumes `$node->name` is always an identifier.
This is fixed by adding an explicit `instanceof` check
before accessing `$node->name->name`.
Added tests fixate this behavior.
Affected matchers:
- MethodCallMatcher
- MethodArgumentDroppedMatcher
Resolves: #108413
Releases: main, 14.0, 13.4Long URLs
Lines exceeding 72 chars are acceptable for URLs. Use numbered references:
This implements the W3C recommendation [1] for accessible forms.
Additional context can be found in the TYPO3 RFC [2].
[1] https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html
[2] https://wiki.typo3.org/Category:T3DD12/Sessions/AccessibilityFooter Tags
Required Format
Tag: value (colon followed by space)
Resolves (REQUIRED)
Closes Forge issue when patch is merged:
Resolves: #12345Multiple issues (one per line):
Resolves: #12345
Resolves: #12346Critical Rule: Every commit MUST have at least one Resolves: line. The commit-msg hook will reject commits without it.
When to use:
- Features: MUST use Resolves
- Tasks: MUST use Resolves
- Bugfixes: MUST use Resolves
- All commit types: ALWAYS use Resolves
Note: For features and tasks, Resolves: closes the issue on merge. For bugfixes, you can use Related: in addition to Resolves: if needed, but Resolves: is still mandatory.
Related (OPTIONAL)
Links issue without closing it:
Related: #12345Critical Rule: Related: CANNOT be used alone - you MUST have at least one Resolves: line in addition to any Related: lines. The commit-msg hook will reject commits with only Related: tags.
When to use (in addition to Resolves):
- Bugfixes: Use Related for issues that should stay open
- Partial fixes: Related for multi-step fixes where issue remains open
- Context: Link related discussions or issues
- Cross-references: Link to related work or documentation
Releases
Target versions (comma-separated):
Releases: main, 13.4, 12.4Format:
main- Current development branch13.4- Patch release version12.4- LTS version
Rules:
- Always include target versions
- List from newest to oldest
- Features go to
mainonly - Bugfixes can target multiple releases
Change-Id
Auto-generated by git commit-msg hook:
Change-Id: I1234567890abcdef1234567890abcdef12345678Critical Rules:
- NEVER manually add Change-Id
- NEVER modify existing Change-Id
- NEVER remove Change-Id
- Git hook generates this automatically
- Required for Gerrit to track patch updates
Depends (Documentation Only)
For documentation patches referencing core changes:
Depends: I1234567890abcdef1234567890abcdef12345678Only used in typo3/cms-docs repository.
Reverts
For reverting patches:
[TASK] Revert "[FEATURE] Introduce YAML imports"
This reverts commit abc123def456.
Resolves: #12347
Reverts: I1234567890abcdef1234567890abcdef12345678Complete Examples
Example 1: Bugfix
[BUGFIX] Prevent null pointer in indexed search
The preg_replace function returns null on PCRE errors like
PREG_BAD_UTF8_ERROR. Passing null to mb_strcut triggers TypeError
in PHP 8.2+.
Add null check with fallback to original content, ensuring type
safety while maintaining graceful degradation for malformed input.
Resolves: #105737
Releases: main, 13.4, 12.4
Change-Id: I1234567890abcdef1234567890abcdef12345678Example 2: Feature
[FEATURE] Add WebP image format support
Implement WebP processing in image manipulation stack:
* Add WebP MIME type detection
* Integrate libwebp encoding/decoding
* Update image quality settings for WebP
* Add configuration options for compression
WebP provides 25-30% better compression than JPEG while maintaining
quality, significantly improving page load times.
Resolves: #98765
Releases: main
Change-Id: Iabcdef1234567890abcdef1234567890abcdef12Example 3: Task with Breaking Change
[!!!][TASK] Drop PHP 7.4 support
PHP 7.4 reached end-of-life in November 2022 and no longer receives
security updates. Remove compatibility code and leverage PHP 8.0+
features:
* Remove PHP 7.4 compatibility polyfills
* Update composer.json to require PHP >= 8.0
* Use union types and match expressions
* Enable strict type declarations globally
Resolves: #99888
Releases: main
Change-Id: I9876543210fedcba9876543210fedcba98765432Example 4: Documentation
[DOCS] Update contribution workflow guide
Clarify git setup instructions and add troubleshooting section
for common SSH key issues reported in #typo3-cms-coredev.
Related: #12345
Releases: main
Change-Id: Iaa11bb22cc33dd44ee55ff66gg77hh88ii99jj00Validation Rules
Subject Line
- [ ] Starts with valid commit type:
[BUGFIX],[FEATURE],[TASK],[DOCS], or[SECURITY] - [ ] Breaking changes use
[!!!]prefix correctly - [ ] Description starts with uppercase letter
- [ ] Uses imperative mood
- [ ] No period at end
- [ ] Length ≤ 52 chars (recommended) or ≤ 72 chars (absolute)
- [ ] No extension names (EXT:) in subject
Body
- [ ] Blank line after subject (if body exists)
- [ ] Lines wrapped at 72 chars (except URLs)
- [ ] Explains how and why, not what
- [ ] No reproduction steps (belong in Forge)
Footer
- [ ]
Resolves:present (REQUIRED for ALL commits)
Critical: The commit-msg hook WILL REJECT commits without at least one Resolves: line
- [ ]
Related:used only in addition toResolves:(optional, cannot be used alone) - [ ]
Releases:present with valid versions - [ ]
Change-Id:present (added by hook) - [ ] Proper format:
Tag: value(colon + space) - [ ] Issue references use
#prefix:#12345
Common Mistakes
❌ Wrong: Vague Subject
[TASK] Improve extension configuration✅ Correct: Specific Subject
[TASK] Add validation for extension configuration arrays---
❌ Wrong: Past Tense
[BUGFIX] Fixed cache invalidation in frontend✅ Correct: Imperative Mood
[BUGFIX] Fix cache invalidation in frontend---
❌ Wrong: No Footer Tags
[FEATURE] Add dark mode support
Implements dark mode toggle with user preference storage.✅ Correct: Complete Footer
[FEATURE] Add dark mode support
Implements dark mode toggle with user preference storage.
Resolves: #12345
Releases: main
Change-Id: I1234567890abcdef1234567890abcdef12345678---
❌ Wrong: Comma-Separated Issues
Resolves: #12345, #12346, #12347✅ Correct: One Per Line
Resolves: #12345
Resolves: #12346
Resolves: #12347---
❌ Wrong: Missing Space After Colon
Releases:main, 13.4✅ Correct: Space After Colon
Releases: main, 13.4Tools
Validation
Use scripts/validate-commit-message.py:
# Validate last commit
./scripts/validate-commit-message.py
# Validate specific file
./scripts/validate-commit-message.py --file commit-msg.txt
# Strict mode (warnings as errors)
./scripts/validate-commit-message.py --strictGeneration
Use scripts/create-commit-message.py:
# Interactive generator
./scripts/create-commit-message.py --issue 105737 --type BUGFIX
# With breaking change
./scripts/create-commit-message.py --issue 98765 --type FEATURE --breakingTemplate
Copy assets/commit-template.txt to ~/.gitmessage.txt:
git config --global commit.template ~/.gitmessage.txtReferences
- Official Guide: https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/Appendix/CommitMessage.html
- Gerrit Documentation: https://review.typo3.org/Documentation/
- TYPO3 Forge: https://forge.typo3.org
Quick Reference
| Element | Format | Example |
|---|---|---|
| Bugfix | [BUGFIX] Description | [BUGFIX] Fix null pointer in indexer |
| Feature | [FEATURE] Description | [FEATURE] Add WebP support |
| Task | [TASK] Description | [TASK] Refactor cache manager |
| Breaking | [!!!][TYPE] Description | [!!!][TASK] Drop PHP 7.4 support |
| Resolves | Resolves: #12345 | Closes issue on merge |
| Related | Related: #12345 | Links without closing |
| Releases | Releases: main, 13.4 | Target versions |
TYPO3 Commit Message Hook
Deep dive into the Build/git-hooks/commit-msg hook: validation rules, error messages, and troubleshooting.
Overview
The commit-msg hook is a Git client-side hook that validates commit messages before they're created. TYPO3 uses this to enforce commit message standards and automatically add Change-Id for Gerrit tracking.
Location: Build/git-hooks/commit-msg Installed to: .git/hooks/commit-msg
Installation
Automated (Recommended)
composer gerrit:setupThis command: 1. Copies hook from Build/git-hooks/commit-msg to .git/hooks/commit-msg 2. Makes it executable 3. Sets up Gerrit configuration
Manual
# Copy hook
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
# Make executable
chmod +x .git/hooks/commit-msgVerify Installation
# Check if hook exists and is executable
ls -la .git/hooks/commit-msg
# Expected output:
# -rwxr-xr-x 1 user group 8192 Dec 15 10:00 .git/hooks/commit-msgHook Functions
1. Change-Id Generation
Purpose: Auto-generate unique Change-Id for Gerrit patch tracking
Function: add_ChangeId()
Behavior:
- Generates unique hash based on commit content
- Adds
Change-Id: I<hash>to commit message footer - Skips if Change-Id already exists
- Skips for fixup!/squash! commits
- Places Change-Id after Resolves/Releases footer
Format:
Change-Id: I1234567890abcdef1234567890abcdef12345678Critical Rules:
- NEVER manually add Change-Id
- NEVER modify existing Change-Id
- NEVER remove Change-Id
- Same Change-Id = same patch (for updates)
- Different Change-Id = new patch
2. Line Length Validation
Function: checkForLineLength()
Rules:
- Maximum line length: 72 characters
- Applies to subject and body
- Excludes comment lines (starting with
#) - URLs can exceed limit
Error Message:
The maximum line length of 72 characters is exceeded.Location: Line 200 in hook
3. Commit Type Validation
Function: checkForCommitType()
Rules:
- First line must contain commit type in brackets
- Valid types:
[BUGFIX],[FEATURE],[TASK],[DOCS],[SECURITY] - Breaking changes:
[!!!][TYPE]
Regex: /^\[^]]+\] .+$/
Error Message:
Your first line has to contain a commit type like '[BUGFIX]'.Location: Line 209 in hook
4. Resolves Tag Validation
Function: checkForResolves()
Rules:
- Every commit MUST have at least one
Resolves:orFixes:line - Format:
Resolves: #<number>orFixes: #<number> - Must be on separate line (not inline)
- Issue number must be numeric
Regex: /^(Resolves|Fixes): #[0-9]+$/
Error Message (as of v1.1):
You need at least one 'Resolves|Fixes: #<issue number>' line.Updated Message (as of v1.2, see Issue #107881):
You need at least one 'Resolves: #<issue number>' line.Location: Line 218 in hook
Important Context:
- The regex accepts both
Resolves:andFixes:for backward compatibility - TYPO3 community standard is to use ONLY
Resolves: - The error message guides users toward the standard
- This was the source of documentation confusion in Issue #105737
5. Releases Tag Validation
Function: checkForReleases()
Rules:
- Every commit MUST have
Releases:line - Format:
Releases: main, 13.4, 12.4(comma-separated) - Valid values:
main, version numbers like13.4,12.4
Regex: /^Releases: (main|[0-9]+\.[0-9])(, *(main|[0-9]+\.[0-9]))*$/
Error Message:
You need a 'Releases:' line. For instance: Releases: main, 8.7Location: Line 227 in hook
Complete Validation Flow
Commit attempted
↓
1. Check line length (≤ 72 chars)
↓
2. Check commit type ([BUGFIX], etc.)
↓
3. Check Resolves/Fixes tag exists
↓
4. Check Releases tag exists
↓
All pass? → Add Change-Id → Commit succeeds
Any fail? → Show errors → Commit rejectedError Messages
Full Error Output
When validation fails:
------------------------------------------------------------------
>> ERROR in your commit message:
- The maximum line length of 72 characters is exceeded.
- You need at least one 'Resolves: #<issue number>' line.
- You need a 'Releases:' line. For instance: Releases: main, 8.7
Please refer to [1] for details on the commit requirements.
You should fix this and then do commit --amend etc.
[1] https://docs.typo3.org/typo3cms/ContributionWorkflowGuide/latest/singlehtml/Index.html#commit-message-rules-for-typo3-cms
------------------------------------------------------------------Individual Errors
| Check | Error Message |
|---|---|
| Line length | The maximum line length of 72 characters is exceeded. |
| Commit type | Your first line has to contain a commit type like '[BUGFIX]'. |
| Resolves tag | You need at least one 'Resolves: #<issue number>' line. |
| Releases tag | You need a 'Releases:' line. For instance: Releases: main, 8.7 |
Troubleshooting
Hook Not Running
Symptom: Commits succeed without validation
Causes: 1. Hook not installed 2. Hook not executable 3. Git hooks disabled
Solutions:
# Check if hook exists
ls -la .git/hooks/commit-msg
# Reinstall hook
composer gerrit:setup
# Verify permissions
chmod +x .git/hooks/commit-msg
# Check Git config (hooks disabled?)
git config --get core.hooksPathHook Rejecting Valid Commit
Symptom: Valid commit message rejected
Debug:
# Test commit message manually
bash .git/hooks/commit-msg .git/COMMIT_EDITMSG
# Check for hidden characters
cat -A .git/COMMIT_EDITMSG
# Verify line endings (should be LF, not CRLF)
file .git/COMMIT_EDITMSGCommon issues:
- Windows line endings (CRLF) instead of Unix (LF)
- Trailing whitespace
- Non-ASCII characters in unexpected places
- Tabs vs spaces
Change-Id Not Generated
Symptom: Commit succeeds but no Change-Id added
Causes: 1. Change-Id already exists (manual addition) 2. Config disables it: git config --get gerrit.createChangeId returns false 3. fixup!/squash! commit (intentionally skipped)
Solutions:
# Enable Change-Id generation
git config gerrit.createChangeId true
# Re-commit to generate
git commit --amend --no-edit
# Verify Change-Id added
git log -1Multiple Change-Ids
Symptom: Commit has multiple Change-Id lines
Impact: Gerrit will reject or behave unexpectedly
Fix:
# Edit commit message
git commit --amend
# Remove duplicate Change-Id lines (keep only one)
# Save and exitHook Customization
When to Customize
Valid reasons:
- Project-specific validation rules
- Additional required tags
- Custom commit message format
Invalid reasons:
- Bypassing validation (use
--no-verifytemporarily instead) - Making validation more lenient (breaks standardization)
Safe Customization Pattern
# 1. Fork the hook
cp .git/hooks/commit-msg .git/hooks/commit-msg.custom
# 2. Add custom validation
# Edit .git/hooks/commit-msg.custom
# 3. Call from main hook
# In .git/hooks/commit-msg, add:
# .git/hooks/commit-msg.custom "$1"
# 4. Document customization
echo "Custom validation: <description>" >> .git/hooks/commit-msg.customExample: Additional Tag
Add optional Sponsored-by: tag:
# Add to checkForResolves() section
checkForSponsor() {
if grep -q -E '^Sponsored-by: ' "$MSG"; then
# Valid sponsor tag found
return 0
fi
}
# Call in validation sequence
checkForLineLength
checkForCommitType
checkForResolves
checkForReleases
checkForSponsor # Custom checkBypassing Hook
When to Bypass
Valid cases:
- Emergency hotfixes
- Rebasing with preserved commits
- Importing historical commits
- Temporary testing
Invalid cases:
- Avoiding fixing commit message
- Regular development workflow
How to Bypass
# Single commit
git commit --no-verify
# Amend without hook
git commit --amend --no-verify
# Rebase without hook
GIT_EDITOR=true git rebase -i HEAD~5 --no-verifyWarning: Gerrit will still reject invalid commits! Bypassing hook locally doesn't bypass Gerrit validation.
Hook History
Version 1.1 (Current)
From: TYPO3 CI Review 1.1 Based on: Gerrit Code Review 2.14.6
Changes from Gerrit original:
- Added line length check (72 chars)
- Added commit type check ([BUGFIX], etc.)
- Added Resolves/Fixes check
- Added Releases check
- Modified Change-Id placement (after footer)
Proposed Version 1.2 (Issue #107881)
Change: Update error message for Resolves check
- Old:
'Resolves|Fixes: #<issue number>' - New:
'Resolves: #<issue number>'
Rationale:
- TYPO3 standard is
Resolves:only Fixes:accepted for backward compatibility only- Error message should guide toward standard practice
Validation regex unchanged: Still accepts both for compatibility
Hook Source Code Structure
Build/git-hooks/commit-msg
├── License header (Apache 2.0)
├── TYPO3 changes documentation
├── add_ChangeId() function
│ ├── clean_message preprocessing
│ ├── Skip conditions (fixup, squash, existing)
│ ├── ID generation
│ └── AWK script for placement
├── _gen_ChangeId() helper
├── _gen_ChangeIdInput() helper
├── Validation functions:
│ ├── checkForLineLength()
│ ├── checkForCommitType()
│ ├── checkForResolves()
│ └── checkForReleases()
├── Validation execution
└── add_ChangeId() callBest Practices
For Contributors
1. Install hook early: First thing after cloning 2. Never bypass: Fix messages instead of bypassing 3. Don't fight the hook: Learn requirements, follow them 4. Use templates: git config commit.template ~/.gitmessage.txt 5. Test locally: Use scripts/validate-commit-message.py
For Maintainers
1. Document changes: Keep this reference updated 2. Test thoroughly: Validate changes don't break existing commits 3. Backward compatible: Keep regex accepting old patterns 4. Clear errors: Make error messages actionable 5. Version carefully: Changes affect all contributors
Related Files
Build/git-hooks/commit-msg- The hook itselfBuild/git-hooks/pre-commit- Code quality checksassets/commit-template.txt- Commit message templatescripts/validate-commit-message.py- Offline validatorreferences/commit-message-format.md- Commit message specification
References
- Hook Source: https://github.com/TYPO3/typo3/blob/main/Build/git-hooks/commit-msg
- Git Hooks Docs: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
- Gerrit Hooks: https://gerrit-review.googlesource.com/Documentation/cmd-hook-commit-msg.html
- Issue #107881: Standardize error message to mention only Resolves
Quick Reference
| Validation | Required Format | Error if Missing |
|---|---|---|
| Commit type | [TYPE] in first line | Yes |
| Line length | ≤ 72 characters | Yes |
| Resolves | Resolves: #123 | Yes |
| Releases | Releases: main | Yes |
| Change-Id | Auto-generated | N/A (added by hook) |
See Also
scripts/validate-commit-message.py- Test messages offlinescripts/create-commit-message.py- Generate compliant messagesassets/commit-template.txt- Pre-filled template
TYPO3 Core Development - Complete DDEV Setup Workflow
Production-tested workflow for setting up a complete TYPO3 Core development environment with DDEV.
Overview
This workflow creates a fully functional TYPO3 Core development environment with:
- TYPO3 Core v14 (main branch)
- PHP 8.4 on Apache with FPM
- MariaDB 10.6
- Test data and styleguide extensions
- Git configured for Gerrit submissions
- Ready for Core development and testing
Prerequisites
Required:
- Git installed and configured
- DDEV installed (https://ddev.readthedocs.io/)
- SSH keys configured for GitHub and Gerrit
Verify DDEV:
ddev version
# Should show DDEV version >= 1.21Complete Setup Workflow
Step 1: Create Project Directory
# Option A: Using 'take' (zsh/oh-my-zsh)
take t3coredev-14-php8-4
# Option B: Standard bash
mkdir -p t3coredev-14-php8-4 && cd t3coredev-14-php8-4Note: Use descriptive directory names indicating TYPO3 version and PHP version.
Step 2: Clone TYPO3 Core Repository
# Clone from GitHub (faster than Gerrit for initial clone)
git clone git@github.com:typo3/typo3 .
# Note: The dot (.) clones into current directoryStep 3: Configure Git for TYPO3 Contributions
# Set your identity
git config user.name "YOUR NAME"
git config user.email "YOUR@EMAIL"
# Enable automatic rebase (required for TYPO3)
git config branch.autosetuprebase remoteStep 4: Install Git Hooks
# Copy commit-msg hook (adds Change-Id)
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
# Alternative: Use composer command
# composer gerrit:setupStep 5: Configure Gerrit Remote
# Set Gerrit as push destination
git config remote.origin.pushurl ssh://YOURT3OUSERNAME@review.typo3.org:29418/Packages/TYPO3.CMS.git
# Configure push refspec for Gerrit review
git config remote.origin.push +refs/heads/main:refs/for/mainImportant: Replace YOURT3OUSERNAME with your actual Gerrit username!
Step 6: Configure DDEV Project
# Set project type to TYPO3
ddev config --project-type typo3 -y
# Configure timezone (adjust to your location)
ddev config --timezone "Europe/Vienna"
# Set PHP version for v14 development
ddev config --php-version=8.4
# Use Apache with FPM (recommended for Core dev)
ddev config --webserver-type=apache-fpm
# Set MariaDB version
ddev config --database=mariadb:10.6PHP Version Notes:
- TYPO3 v14: PHP 8.2, 8.3, 8.4
- TYPO3 v13: PHP 8.1, 8.2, 8.3
- Check
composer.jsonfor exact requirements
Step 7: Configure DDEV Environment Variables
# Set TYPO3 context to Development/Ddev
ddev config --web-environment-add="TYPO3_CONTEXT=Development/Ddev"
# Set Composer root version for dev branch
ddev config --web-environment-add="COMPOSER_ROOT_VERSION=14.0.x-dev"Context Meanings:
Development/Ddev: Enables debugging, disables cachingProduction: Live site configurationTesting: For automated test environments
Step 8: Start DDEV
ddev startWhat happens: 1. Creates Docker containers (web, db, phpmyadmin) 2. Configures networking 3. Sets up SSL certificates 4. Mounts project directory
Expected output:
Starting t3coredev-14-php8-4...
Successfully started t3coredev-14-php8-4
Project can be reached at https://t3coredev-14-php8-4.ddev.siteStep 9: Install Dependencies
# Use TYPO3's runTests.sh script (preferred for Core dev)
./Build/Scripts/runTests.sh -s composerInstall
# Alternative: Direct composer command
# ddev composer installWhy runTests.sh?
- Ensures correct Composer flags
- Consistent with CI environment
- Handles Core-specific requirements
Step 10: Prepare TYPO3 Installation
# Create installation trigger file
ddev exec 'touch /var/www/html/FIRST_INSTALL'
# Enable Install Tool
ddev exec 'touch /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
ddev exec 'echo "KEEP_FILE" > /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'File purposes:
FIRST_INSTALL: Triggers installation wizardENABLE_INSTALL_TOOL: Enables Install Tool access (with KEEP_FILE prevents auto-deletion)
Step 11: Run TYPO3 Setup
ddev typo3 setup \
--driver=mysqli \
--host=db \
--port=3306 \
--dbname=db \
--username=db \
--password=db \
--admin-username=backenduser \
--admin-user-password='YOUR_SECURE_PASSWORD' \
--admin-email='YOUR@EMAIL' \
--project-name='TYPO3 Core Dev v14 PHP 8.4' \
--no-interaction \
--server-type=apache \
--forceImportant:
- Replace
YOUR_SECURE_PASSWORDwith your preferred admin password - Replace
YOUR@EMAILwith your email - Database credentials (db/db/db) are DDEV defaults
What this creates:
- Database tables and schema
- Backend admin user account
- Basic TYPO3 configuration
- AdditionalConfiguration.php
Step 12: Activate Core Extensions
# Set up extensions first
ddev typo3 extension:setup
# Activate indexed_search (relevant for testing search functionality)
ddev typo3 extension:activate indexed_search
# Activate styleguide (provides test data and UI components)
ddev typo3 extension:activate styleguide
# Activate scheduler (for scheduled tasks)
ddev typo3 extension:activate schedulerExtension purposes:
indexed_search: Full-text search (relevant to bug #105737!)styleguide: Test data generator, UI component showcasescheduler: Cron-like task scheduling
Step 13: Configure Backend User Groups
ddev typo3 setup:begroups:default --groups=BothCreates:
- Editor group (content management)
- Advanced Editor group (extended permissions)
- Assigns both groups to admin user
Step 14: Generate Test Data
# Generate TCA (Table Configuration Array) examples
ddev typo3 styleguide:generate --create -- tca
# Generate frontend system template
ddev typo3 styleguide:generate --create -- frontend-systemplateTest data includes:
- All TCA field types with examples
- Content elements with various configurations
- Pages with different properties
- Frontend templates and TypoScript
Step 15: Launch TYPO3 Backend
ddev launch /typo3Opens TYPO3 backend in your default browser.
Login credentials:
- Username:
backenduser - Password: Whatever you set in Step 11
Post-Setup Verification
Verify Installation
Check TYPO3 is running:
ddev launchAccess Install Tool:
ddev launch /typo3/install.phpView site info:
ddev describeVerify Git Configuration
# Check user config
git config user.name
git config user.email
# Check Gerrit config
git config remote.origin.pushurl
git config remote.origin.push
# Verify hooks
ls -la .git/hooks/commit-msgVerify DDEV Configuration
# View DDEV config
cat .ddev/config.yaml
# Should show:
# - project_type: typo3
# - php_version: "8.4"
# - webserver_type: apache-fpm
# - database: mariadb:10.6
# - web_environment:
# - TYPO3_CONTEXT=Development/Ddev
# - COMPOSER_ROOT_VERSION=14.0.x-devTest Core Functionality
Access frontend:
ddev launchRun tests:
# Unit tests
./Build/Scripts/runTests.sh -s unit
# Functional tests
./Build/Scripts/runTests.sh -s functional
# Check available test suites
./Build/Scripts/runTests.sh -hDevelopment Workflow
Creating a Feature Branch
# Ensure main is up-to-date
git checkout main
git pull origin main
# Create feature branch
git checkout -b feature/105737-fix-indexed-search-crashMaking Changes
# Make code changes
vim typo3/sysext/indexed_search/Classes/Indexer.php
# Stage changes
git add .
# Commit with proper message
git commit
# (Use commit message template)Testing Changes
# Run relevant tests
./Build/Scripts/runTests.sh -s unit -- \
typo3/sysext/indexed_search/Tests/Unit/
# Check code style
./Build/Scripts/runTests.sh -s cgl -nSubmitting to Gerrit
# Push to Gerrit for review
git push origin HEAD:refs/for/mainUseful DDEV Commands
Project Management
# Start project
ddev start
# Stop project
ddev stop
# Restart project
ddev restart
# Delete project (keeps files)
ddev delete
# Power off all DDEV projects
ddev poweroffDatabase Management
# Export database
ddev export-db --file=backup.sql.gz
# Import database
ddev import-db --file=backup.sql.gz
# Access database CLI
ddev mysql
# Launch phpMyAdmin
ddev launch -pTYPO3 Commands
# Clear all caches
ddev typo3 cache:flush
# Clear specific cache
ddev typo3 cache:flush --group=system
# Run scheduler tasks
ddev typo3 scheduler:run
# List available commands
ddev typo3 listDebugging
# View logs
ddev logs
# Follow logs (like tail -f)
ddev logs -f
# SSH into container
ddev ssh
# Execute command in container
ddev exec 'command'Performance
# View resource usage
docker stats
# Restart services if slow
ddev restartCustomization Options
Different PHP Versions
# Switch to PHP 8.2
ddev config --php-version=8.2
ddev restartDifferent Database Versions
# Use MySQL instead of MariaDB
ddev config --database=mysql:8.0
ddev restartAdditional Services
# Add Redis
ddev get ddev/ddev-redis
# Add Elasticsearch
ddev get ddev/ddev-elasticsearch
# Add Mailhog (email testing)
ddev config --mailhog-port=8026Custom Domain
# Add additional hostname
ddev config --additional-hostnames=t3dev.local
ddev restartTroubleshooting
"Port already allocated"
Problem: DDEV can't start because ports are in use
Solution:
# Stop other DDEV projects
ddev poweroff
# Or change port
ddev config --router-http-port=8080 --router-https-port=8443"Composer timeout"
Problem: Composer operations timeout
Solution:
# Increase timeout
ddev composer config --global process-timeout 2000
# Or use runTests.sh
./Build/Scripts/runTests.sh -s composerInstall"Cannot write to directory"
Problem: Permission issues in container
Solution:
# Fix permissions
ddev exec 'chmod -R 777 var/ typo3temp/ typo3conf/'
# Or restart DDEV
ddev restart"Database connection failed"
Problem: TYPO3 can't connect to database
Solution:
# Check database is running
ddev describe
# Verify credentials in LocalConfiguration.php
ddev exec 'cat typo3conf/LocalConfiguration.php | grep -A5 DB'
# Should show: host=db, username=db, password=db, database=dbBest Practices
Directory Naming
Use descriptive names indicating:
- TYPO3 version:
t3coredev-14 - PHP version:
php8-4 - Purpose:
coredev,testing,feature-name
Examples:
t3coredev-14-php8-4t3-bugfix-105737t3-testing-indexed-search
Git Workflow
1. Always start from main:
git checkout main
git pull origin main2. Use feature branches:
git checkout -b feature/issue-description3. Keep single commit per patch:
git commit --amend # Update existing commit4. Rebase regularly:
git fetch origin
git rebase origin/mainDDEV Management
1. Stop unused projects:
ddev list
ddev stop <project>2. Clean up old projects:
ddev delete <project>
# Then manually delete directory3. Monitor resources:
docker statsTesting Workflow
1. Test before committing:
./Build/Scripts/runTests.sh -s unit
./Build/Scripts/runTests.sh -s functional2. Check code style:
./Build/Scripts/runTests.sh -s cgl -n3. Fix code style automatically:
./Build/Scripts/runTests.sh -s cglQuick Reference
Essential Commands
| Task | Command |
|---|---|
| Start DDEV | ddev start |
| Stop DDEV | ddev stop |
| Open backend | ddev launch /typo3 |
| Clear cache | ddev typo3 cache:flush |
| Run unit tests | ./Build/Scripts/runTests.sh -s unit |
| Install composer | ./Build/Scripts/runTests.sh -s composerInstall |
| View logs | ddev logs -f |
| SSH into container | ddev ssh |
| Export database | ddev export-db --file=backup.sql.gz |
| Git push to Gerrit | git push origin HEAD:refs/for/main |
TYPO3 Versions & PHP Compatibility
| TYPO3 Version | PHP Versions | Branch | Status |
|---|---|---|---|
| v14 (main) | 8.2, 8.3, 8.4 | main | Development |
| v13 (LTS) | 8.1, 8.2, 8.3 | 13.4 | Active |
| v12 (ELTS) | 8.1, 8.2 | 12.4 | Security only |
Default Credentials
| Service | URL | Username | Password |
|---|---|---|---|
| Backend | https://[project].ddev.site/typo3 | backenduser | (your choice) |
| Database | db:3306 | db | db |
| phpMyAdmin | https://[project].ddev.site:8037 | db | db |
Integration with typo3-ddev-skill
This workflow complements the typo3-ddev-skill:
- Use
typo3-ddev-skillfor quick setup automation - Use this workflow for manual step-by-step understanding
- Both produce equivalent development environments
Additional Resources
- DDEV Documentation: https://ddev.readthedocs.io/
- TYPO3 Development: https://docs.typo3.org/m/typo3/reference-coreapi/
- runTests.sh Guide: https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/Testing/
- TYPO3 Slack: https://typo3.slack.com (#typo3-cms-coredev)
---
Note: This workflow is based on proven production usage and is continuously updated for current TYPO3 versions. Always check official documentation for the latest recommendations.
Forge REST API Documentation
Complete guide to using the TYPO3 Forge (Redmine) REST API for programmatic issue management.
Overview
TYPO3 Forge (https://forge.typo3.org) is built on Redmine and exposes a REST API for:
- Creating issues
- Updating issues
- Querying project metadata
- Managing issue relationships
Authentication
Get API Key
1. Log in to https://forge.typo3.org 2. Go to https://forge.typo3.org/my/account 3. Find "API access key" on the right side 4. Click "Show" to reveal your key 5. Store securely (treat like a password!)
Using API Key
IMPORTANT: Different operations require different authentication methods!
For GET Requests (Reading)
Pass via HTTP header:
-H "X-Redmine-API-Key: your-api-key-here"For POST Requests (Creating Issues)
Header authentication does NOT work for creating issues! Use HTTP Basic Auth with API key as username:
curl -u "your-api-key-here:x" \
-H "Content-Type: application/json" \
-X POST \
-d '{"issue": {...}}' \
https://forge.typo3.org/issues.jsonNote: The password can be anything (we use "x") - only the API key as username matters.
For PUT Requests (Updating Issues)
Warning: PUT requests may return 403 Forbidden depending on your account permissions. Some accounts can create issues but not update them via API. If you get 403 on PUT, you'll need to update issues manually through the web interface.
# This may return 403 depending on permissions
curl -u "your-api-key-here:x" \
-H "Content-Type: application/json" \
-X PUT \
-d '{"issue": {"description": "..."}}' \
https://forge.typo3.org/issues/12345.jsonSecurity: Never commit API keys to repositories. Use environment variables:
export FORGE_API_KEY="your-api-key-here"Base URL
All API endpoints use:
https://forge.typo3.orgCommon Endpoints
Create Issue
Endpoint: POST /issues.json
Request:
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Issue title here",
"description": "Detailed description",
"tracker_id": 1,
"category_id": 975,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "13"}
]
}
}' \
https://forge.typo3.org/issues.jsonResponse:
{
"issue": {
"id": 107881,
"project": {"id": 27, "name": "TYPO3 Core"},
"tracker": {"id": 1, "name": "Bug"},
"status": {"id": 1, "name": "New"},
"priority": {"id": 4, "name": "Should have"},
"subject": "Issue title here",
"description": "Detailed description",
"created_on": "2024-12-15T10:30:00Z",
"updated_on": "2024-12-15T10:30:00Z"
}
}Extract Issue Number:
# Parse with jq
curl ... | jq -r '.issue.id'
# Parse with grep
curl ... | grep -oP '"id":\K[0-9]+' | head -1Get Project Metadata
Endpoint: GET /projects/typo3cms-core.json
Request:
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
https://forge.typo3.org/projects/typo3cms-core.jsonResponse includes:
- Available trackers (Bug, Feature, Task, etc.)
- Issue categories (Backend, Frontend, etc.)
- Custom field definitions
Get Issue Details
Endpoint: GET /issues/{id}.json
Request:
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
https://forge.typo3.org/issues/105737.jsonResponse includes:
- Full issue details
- Custom fields
- Status and assignments
- Related issues
Field IDs
Trackers
| ID | Name |
|---|---|
| 1 | Bug |
| 2 | Feature |
| 4 | Task |
| 6 | Story |
| 10 | Epic |
Priorities
| ID | Name |
|---|---|
| 2 | Nice to have |
| 3 | Must have |
| 4 | Should have |
| 5 | Could have |
Common Categories
| ID | Name |
|---|---|
| 971 | Backend API |
| 972 | Backend User Interface |
| 973 | Caching |
| 974 | Database API (Doctrine DBAL) |
| 975 | Miscellaneous |
| 976 | Extension Manager |
| 977 | Frontend |
| 1000 | Indexed Search |
| 1003 | Content Rendering |
| 1004 | Documentation |
Get full list: Use scripts/query-forge-metadata.sh
Custom Fields
| ID | Name | Purpose |
|---|---|---|
| 3 | Tags | Comma-separated keywords |
| 4 | TYPO3 Version | Version affected (e.g., "13", "12") |
| 5 | PHP Version | PHP version (e.g., "8.2", "8.3") |
| 8 | Complexity | Complexity estimate |
| 15 | Is Regression | Whether it's a regression |
| 18 | Sprint Focus | Sprint assignment |
Complete Examples
Example 1: Create Bug Report
export FORGE_API_KEY="your-api-key-here"
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Indexed search causes crash on malformed UTF-8",
"description": "When processing content with malformed UTF-8, the indexed search indexer crashes with TypeError in PHP 8.2+.\n\nSteps to reproduce:\n1. Create page with malformed UTF-8 content\n2. Run indexer\n3. Observe crash\n\nExpected: Graceful handling\nActual: TypeError exception",
"tracker_id": 1,
"category_id": 1000,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "13"},
{"id": 5, "value": "8.2"},
{"id": 3, "value": "indexed search, UTF-8, crash"}
]
}
}' \
https://forge.typo3.org/issues.jsonExample 2: Create Feature Request
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Add WebP image format support",
"description": "Add native WebP support to TYPO3 image processing:\n\n- WebP MIME type detection\n- Image manipulation support\n- Configuration options\n\nBenefit: 25-30% better compression than JPEG",
"tracker_id": 2,
"category_id": 977,
"priority_id": 5,
"custom_fields": [
{"id": 4, "value": "14"},
{"id": 3, "value": "WebP, images, performance"}
]
}
}' \
https://forge.typo3.org/issues.jsonExample 3: Create Task
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d '{
"issue": {
"project_id": "typo3cms-core",
"subject": "Standardize commit-msg hook error message",
"description": "Update Build/git-hooks/commit-msg error message to mention only '\''Resolves:'\'' tag instead of '\''Resolves|Fixes:'\'' to align with TYPO3 community standard.\n\nWhile validation regex accepts both for backward compatibility, error message should guide toward single standard keyword.",
"tracker_id": 4,
"category_id": 975,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "14"}
]
}
}' \
https://forge.typo3.org/issues.jsonError Handling
Common Errors
422 Unprocessable Entity:
{
"errors": ["Category is not included in the list"]
}Solution: Check field IDs are valid for the project
422 - Required Field Missing:
{
"errors": ["Typo3 version cannot be blank"]
}Solution: Add required custom field (id: 4 for TYPO3 version)
401 Unauthorized:
{"errors": ["You are not authorized to access this page."]}Solution: Check API key is correct and has permissions
Validation
Before creating issue, validate:
- [ ] API key is set and valid
- [ ] project_id is "typo3cms-core"
- [ ] subject is descriptive (not too generic)
- [ ] tracker_id is valid (1, 2, or 4 most common)
- [ ] category_id matches project categories
- [ ] TYPO3 version custom field included (id: 4)
Best Practices
Subject Lines
✅ Good:
- "Indexed search crashes on malformed UTF-8"
- "Add WebP image format support"
- "Standardize commit-msg hook error message"
❌ Bad:
- "Fix bug"
- "Improvement needed"
- "Question about feature"
Descriptions
Structure: 1. Brief summary (what is the issue) 2. Steps to reproduce (for bugs) 3. Expected behavior 4. Actual behavior 5. Additional context
Include:
- Error messages
- Stack traces
- Version information
- Configuration details
Avoid:
- Asking questions (use Slack instead)
- Multiple unrelated issues in one ticket
- Vague descriptions without details
Categories
Choose most specific category:
- Not "Miscellaneous" if more specific exists
- "Backend API" for backend PHP code
- "Backend User Interface" for backend UI/UX
- "Frontend" for frontend rendering
- Component-specific for extensions (e.g., "Indexed Search")
Priority
Guidelines:
- Must have (3): Blocking issues, critical bugs, security
- Should have (4): Normal bugs, important features (most common)
- Could have (5): Nice-to-have features, minor improvements
- Nice to have (2): Low priority, future considerations
Automation Tips
Store Issue Template
cat > /tmp/issue-template.json <<'EOF'
{
"issue": {
"project_id": "typo3cms-core",
"subject": "",
"description": "",
"tracker_id": 1,
"category_id": 975,
"priority_id": 4,
"custom_fields": [
{"id": 4, "value": "13"}
]
}
}
EOFParse Response
# Extract issue number
ISSUE_ID=$(curl ... | jq -r '.issue.id')
# Build URL
ISSUE_URL="https://forge.typo3.org/issues/${ISSUE_ID}"
# Use in commit message
echo "Resolves: #${ISSUE_ID}"Batch Operations
Query multiple issues:
for id in 105737 107881 108000; do
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
"https://forge.typo3.org/issues/${id}.json"
doneIntegration with Git
Create Issue and Commit
#!/bin/bash
# Create issue
RESPONSE=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d @issue.json \
https://forge.typo3.org/issues.json)
# Extract issue number
ISSUE_ID=$(echo "$RESPONSE" | jq -r '.issue.id')
# Use in commit
git commit -m "[BUGFIX] Fix the problem
Resolves: #${ISSUE_ID}
Releases: main"Link Gerrit Patch to Issue
After submitting to Gerrit:
GERRIT_URL="https://review.typo3.org/c/Packages/TYPO3.CMS/+/91302"
# Add comment to Forge issue with patch link
# (requires additional API call - see Redmine API docs)Resources
- Redmine API Docs: https://www.redmine.org/projects/redmine/wiki/Rest_api
- TYPO3 Forge: https://forge.typo3.org
- API Access Key: https://forge.typo3.org/my/account
- Project Info: https://forge.typo3.org/projects/typo3cms-core
Quick Reference
| Task | Endpoint | Method |
|---|---|---|
| Create issue | /issues.json | POST |
| Get issue | /issues/{id}.json | GET |
| Update issue | /issues/{id}.json | PUT |
| Get project | /projects/{id}.json | GET |
| List issues | /issues.json?project_id=typo3cms-core | GET |
See Also
scripts/create-forge-issue.sh- Interactive issue creationscripts/query-forge-metadata.sh- Query project metadatareferences/commit-message-format.md- For using issue numbers in commits
TYPO3 Gerrit Review Patterns - Real-World Insights
Based on analysis of actual merged patches from review.typo3.org, this document captures common review patterns, expectations, and best practices.
Understanding Revision Counts
Key Insight: High revision counts (7-25 patch sets) are NORMAL and expected, not a sign of failure.
Real Examples:
- Change #90226: 24 patch sets (functional tests for Extbase FileUpload)
- Change #88519: 14 patch sets (breaking change - Record API)
- Change #91161: 9 patch sets (DI refactoring)
- Change #91284: 7 patch sets (pagetree performance)
What causes multiple revisions: 1. CI failures requiring fixes 2. Rebases due to base branch updates 3. Code quality refinements based on reviewer feedback 4. Architectural improvements suggested by core team 5. Edge case handling 6. Scope adjustments (e.g., backport constraints)
Mindset: Each revision makes the patch better. Multiple revisions show:
- Responsiveness to feedback
- Iterative improvement
- Collaboration with core team
- Thorough vetting process
Common Reviewer Feedback Themes
1. Architectural Alignment
Pattern: Leverage framework patterns over custom solutions
Example from #91161:
Reviewer: "Just make this method no-op with only the trigger_error() and
remove $this->instances since the service locator will catch classes that
implements the interface automatically."Expectation:
- Use dependency injection (DI) over manual instance management
- Leverage service locators for interface-based registration
- Follow TYPO3 framework patterns
- Avoid reinventing framework capabilities
Best Practice: Before implementing, check if TYPO3 framework already provides the pattern.
2. Configuration Best Practices
Pattern: Services.yaml configuration matters
Example from #90226:
Reviewer: "Why is Domain/Validator/ excluded here? This would prevent
validators from receiving dependency injection."Expectation:
- Understand Services.yaml exclusion patterns
- Don't copy boilerplate without understanding
- Enable DI for all appropriate classes
- Reference official documentation for patterns
Best Practice: Review Services.yaml carefully, don't blindly copy from examples.
3. Performance Validation
Pattern: Performance claims require empirical evidence
Example from #91284:
Reviewer: "It performs much better. If a large number of pages are open,
there is still a very slight delay (milliseconds), but this should not
cause any problems."
Reviewer: "Nice one here, removing the expensive calls to this.nodes.find()
- reduced from O(n^(m²)) [30ms] to O(n) [2-3ms]"Expectation:
- Test performance fixes in production-like environments
- Provide computational complexity analysis
- Measure actual performance improvements
- Document before/after metrics
- Multiple reviewers test independently
Best Practice: Include performance measurements in commit message or comments.
4. Breaking Changes Documentation
Pattern: Breaking changes need explicit communication
Example from #88519:
Reviewer: "The info that the item.record is now a record object, is
important to know for externals."
Reviewer: "That's breaking IMHO" (regarding API change)Expectation:
- Document API changes affecting extension developers
- Use
[!!!]prefix for breaking changes - Add deprecations with
trigger_error()for BC breaks - Consider backport constraints (may limit to main branch)
- Provide migration examples
Best Practice: Always think about extension developers when changing public APIs.
5. Code Quality Standards
Pattern: Modern PHP practices and clean code
Recurring feedback themes:
- Use named arguments in function calls
- Separate concerns (split large functions into classes)
- Improve readability through refactoring
- Handle edge cases explicitly
- Remove unused code
Best Practice: Follow PSR-12 and modern PHP 8+ features.
6. Test Stability Focus
Pattern: Tests serve as API stability monitors
Example from #90226:
Reviewer: "These tests could serve as an important whistleblower with
extbase to monitor API stability and how frequently changes are needed."Expectation:
- Tests should catch unintended API changes
- Test scope should be "as simple as possible and as complex as needed"
- Functional tests preferred over unit tests for integration points
- Tests validate real-world usage patterns
Best Practice: Write tests that detect breaking changes, not just code coverage.
7. Iterative Refinement Philosophy
Pattern: Patches improve through collaboration, not rejection
Observed patterns:
- Positive language: "I like! 🙌", "Awesome job!", "Nice one here"
- Constructive suggestions: "You could...", "Consider...", "What about..."
- Collaborative problem-solving: Multiple reviewers contribute ideas
- Incremental improvements: Each revision refines the approach
Expectation:
- Be responsive to feedback
- Implement suggested improvements
- Ask clarifying questions when needed
- Iterate toward excellence
Best Practice: View reviews as mentoring, not gatekeeping.
Common Revision Patterns
Pattern 1: CI Failure Cycle
Typical flow: 1. Initial submission 2. CI rejects (CGL, PHPStan, tests) 3. Fix CI issues 4. Resubmit 5. New CI issues found 6. Repeat until green
Prevention: Use typo3-conformance-skill and typo3-testing-skill BEFORE first submission.
Pattern 2: Rebase Cycle
Typical flow: 1. Patch submitted 2. Base branch updated with other changes 3. Gerrit shows "needs rebase" 4. Rebase on latest main 5. Resolve conflicts 6. CI runs again (may reveal new issues) 7. Repeat as needed
Prevention: Rebase regularly during development, not just at submission.
Pattern 3: Scope Adjustment
Typical flow: 1. Patch targets main + version branches 2. Review reveals backport complexity 3. Dependencies on other changes discovered 4. Scope changed to "main only" 5. Commit message updated
Prevention: Check dependencies before claiming backport compatibility.
Pattern 4: Architecture Refinement
Typical flow: 1. Working implementation submitted 2. Reviewer suggests better framework pattern 3. Refactor to use framework capabilities 4. Simplify code by removing custom logic 5. May take 3-5 revisions to align
Prevention: Study framework patterns before implementing custom solutions.
Review Timeline Expectations
Based on analyzed patches:
Simple changes (1-3 files, no breaking changes):
- Review starts: Within 1-2 days
- First feedback: 2-3 days
- Typical revisions: 2-5 patch sets
- Merge time: 1-2 weeks
Complex changes (multiple files, new features):
- Review starts: Within 3-5 days
- First feedback: 3-7 days
- Typical revisions: 7-15 patch sets
- Merge time: 2-4 weeks
Breaking changes (API changes, [!!!]):
- Review starts: Within 1-2 days
- First feedback: 1-3 days (architectural concerns raised early)
- Typical revisions: 10-20 patch sets
- Merge time: 3-6 weeks (due to documentation, deprecation)
Performance fixes:
- Review starts: Within 1-2 days
- Testing phase: 1-2 weeks (reviewers test in production)
- Typical revisions: 5-10 patch sets
- Merge time: 2-3 weeks
Key Reviewers and Their Focus Areas
Based on observed patterns:
Christian Kuhn (lolli):
- Architectural alignment
- Framework pattern usage
- Test quality and coverage
- Long-term maintainability
Benni Mack:
- Breaking change implications
- Extension developer impact
- API design
- Documentation completeness
Stefan Bürk:
- Configuration best practices
- Services.yaml patterns
- Dependency injection
- Code quality standards
Pattern: Different reviewers have different expertise areas. Address each reviewer's specific concerns.
Best Practices from Real Reviews
Do's
✅ Respond to every comment: Even if just "Done" or "Fixed in PS X" ✅ Test in production-like environments: Especially for performance fixes ✅ Use framework patterns: DI, service locators, event dispatchers ✅ Document breaking changes: Think about extension developers ✅ Iterate based on feedback: Don't defend, improve ✅ Keep scope focused: Don't expand scope during review ✅ Update commit messages: Reflect scope or approach changes ✅ Add deprecations properly: Use trigger_error() for BC breaks
Don'ts
❌ Don't take high revision counts personally: They're normal and expected ❌ Don't copy boilerplate blindly: Understand configuration patterns ❌ Don't skip testing: CI will catch it anyway ❌ Don't ignore architectural feedback: Core team guides for good reasons ❌ Don't rush rebases: Test after rebasing ❌ Don't claim performance without metrics: Provide evidence ❌ Don't break APIs without [!!!]: Use proper prefixes ❌ Don't argue with multiple reviewers: If 2+ reviewers agree, they're probably right
Handling Common Situations
Situation 1: "My patch has 10 revisions already"
Response: This is normal! Changes #90226 had 24, #88519 had 14. Keep iterating.
Action: 1. Review all outstanding comments 2. Address each systematically 3. Test thoroughly after each change 4. Mark comments as resolved with explanation 5. Keep positive attitude
Situation 2: "Reviewer suggested complete refactoring"
Response: Core team is guiding toward better patterns. This is mentoring.
Action: 1. Ask clarifying questions if needed 2. Study the suggested pattern 3. Implement as suggested 4. Don't defend original approach 5. Learn framework patterns for future
Situation 3: "CI keeps failing after fixes"
Response: Each rebase can reveal new issues. This is expected.
Action: 1. Use typo3-conformance-skill locally 2. Use typo3-testing-skill for test failures 3. Validate BEFORE pushing 4. Consider environment differences 5. Ask for help if stuck
Situation 4: "Scope changed from 'main + 13.4' to 'main only'"
Response: Backport complexity discovered during review. Common pattern.
Action: 1. Update commit message (Releases: main) 2. Update Forge issue target version 3. Don't argue - backporting is complex 4. Focus on getting main merged first 5. Backport can be separate patch later
Learning from Reviews
What to Extract from Reviews
When reading other reviews: 1. Architectural patterns: How do they structure code? 2. Framework usage: What TYPO3 APIs do they leverage? 3. Testing approaches: How do they test complex scenarios? 4. Documentation style: How do they explain breaking changes? 5. Reviewer priorities: What concerns get raised most?
How to Improve
Based on review patterns: 1. Study merged patches: See what passes review 2. Read reviewer comments: Learn what matters to core team 3. Use framework patterns: Follow existing approaches 4. Test thoroughly: Validate locally before submission 5. Be responsive: Quick turnaround on feedback 6. Stay positive: Reviews are mentoring, not rejection
Summary: Review Success Pattern
Before submission:
- ✅ Use typo3-conformance-skill
- ✅ Use typo3-testing-skill
- ✅ Study framework patterns
- ✅ Check Services.yaml configuration
- ✅ Test in realistic environment
During review:
- ✅ Respond to all comments promptly
- ✅ Implement suggestions positively
- ✅ Test after each revision
- ✅ Update commit message as needed
- ✅ Ask questions when unclear
Mindset:
- ✅ Multiple revisions are normal and healthy
- ✅ Reviews improve your code
- ✅ Core team is mentoring you
- ✅ Each iteration makes TYPO3 better
- ✅ You're learning framework patterns
References
Analyzed patches:
- #90226: Extbase FileUpload functional tests (24 PS)
- #91161: DI in ExtractorService (9 PS)
- #91284: Pagetree performance (7 PS)
- #88519: Record API breaking change (14 PS)
Review platform: https://review.typo3.org
Remember: The best contributors don't have the fewest revisions - they have the most responsive and collaborative review interactions.
TYPO3 Gerrit Workflow Guide
Comprehensive guide for working with Gerrit code review system in TYPO3 Core contributions.
What is Gerrit?
Gerrit is a web-based code review tool that TYPO3 uses for managing patch submissions. Every code change must go through Gerrit review before being merged into TYPO3 Core.
Key Concepts:
- Patch/Change: Single commit representing your contribution
- Patchset: Version of a patch (same Change-Id, updated code)
- Review: Process of evaluating code quality and correctness
- Merge: Final acceptance of patch into TYPO3 Core
Accessing Gerrit
URL: https://review.typo3.org
Authentication: Use TYPO3.org account credentials
Search Tool: https://forger.typo3.com (easier searching)
Submitting Your First Patch
Prerequisites
- Git configured for TYPO3 (see Environment Setup)
- SSH keys added to Gerrit
- Commit ready with proper message format
- All changes in single commit
Push to Gerrit
# From your feature branch
git push origin HEAD:refs/for/mainWhat happens: 1. Git pushes to special Gerrit ref: refs/for/main 2. Gerrit creates new review 3. You receive SUCCESS message with review URL
Expected Output:
remote: Processing changes: new: 1, done
remote:
remote: SUCCESS
remote:
remote: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345 [NEW]
remote:
To ssh://review.typo3.org:29418/Packages/TYPO3.CMS.git
* [new branch] HEAD -> refs/for/mainSave the review URL! You'll need it to monitor progress.
Alternative Push Methods
If you've configured default push settings:
# Simple push (if remote.origin.push configured)
git pushContinuous Integration
After submission, Gerrit automatically runs tests:
1. GitLab CI triggers test pipeline 2. Tests run across multiple PHP versions, code style checks, static analysis (PHPStan), and unit/functional tests 3. Results appear on Gerrit review page 4. Usually completes in 10-20 minutes
Status Indicators:
- ✅ Green checkmark: All tests passed
- ❌ Red X: Tests failed
- ⏳ Clock: Tests running
IMPORTANT: New Patches Start in "Work in Progress" State
By default, newly submitted patches are marked as WIP (Work in Progress). This means:
1. ⚠️ Not visible to reviewers - Core team won't see your patch for review 2. ✅ CI tests still run - You get test feedback immediately 3. 🔍 You must verify yourself first - Check all CI jobs before requesting review 4. ✅ You must manually mark as ready - Change state to "Ready for Review" when done
Workflow for New Submissions:
# 1. Push your patch
git push origin HEAD:refs/for/main
# 2. Note the review URL from output
# https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345 [WIP]
# 3. Wait for CI to complete (10-20 minutes)
# 4. CHECK ALL FAILING JOBS (critical step!)
# - Open the review URL
# - Look for any CI failures (red X marks)
# - For each failure, find the GitLab job URL
# - Read the ACTUAL ERROR LOGS (don't guess!)
# - Fix ALL issues before marking ready
# 5. Once all tests pass, mark as ready for review
#
# Option A: Remove WIP via command line (empty push)
git commit --amend --allow-empty --no-edit
git push origin HEAD:refs/for/main%ready
# Option B: Remove WIP via web UI
# a. Open review URL: https://review.typo3.org/c/Packages/TYPO3.CMS/+/XXXXX
# b. Click "Start Review" button (top right area, near your avatar)
#
# Notes:
# - %ready flag removes WIP state (even with empty pushes)
# - %wip flag sets WIP state: git push origin HEAD:refs/for/main%wip
# - SSH 'gerrit review' command does NOT support WIP flags (use git push flags instead)Investigating CI Failures (CRITICAL!)
NEVER assume what failed - ALWAYS check the actual job logs!
Step 1: Find All Failing Jobs
On your Gerrit review page: 1. Scroll to the CI results section 2. Look for red ❌ marks next to job names 3. Note ALL failing job names (there might be multiple!)
Common failing jobs:
cgl pre-merge- Code style violations (PHP CS Fixer)phpstan php X.X pre-merge- Static analysis errorsunit php X.X pre-merge- Unit test failuresfunctional php X.X pre-merge- Functional test failures
Step 2: Access GitLab Job Logs
For each failing job: 1. Click on the failing job name in Gerrit 2. You'll be redirected to GitLab CI (https://git.typo3.org/typo3/CI/cms/-/jobs/XXXXXX) 3. Click the job log or raw log to see the actual error
Example: If job #4896429 failed:
- URL:
https://git.typo3.org/typo3/CI/cms/-/jobs/4896429 - Raw log:
https://git.typo3.org/typo3/CI/cms/-/jobs/4896429/raw
Step 3: Read and Understand ACTUAL Errors
DO NOT GUESS! Read the actual error messages:
Code Style (cgl) Example:
Fixed 1 of 1 files in ... seconds.
Checked 1 of 1 files in ... seconds.
1) typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php (single_quote)
---------- begin diff ----------
- body: "This content should not appear"
+ body: 'This content should not appear'
----------- end diff -----------Fix: Change double quotes to single quotes in test file.
PHPStan Example:
------ -------------------------------------------------------------------------
Line indexed_search/Tests/Unit/IndexerTest.php
------ -------------------------------------------------------------------------
236 Call to static method PHPUnit\Framework\Assert::assertNotNull()
with string will always evaluate to true.
------ -------------------------------------------------------------------------Fix: Remove assertNotNull() call - it's redundant for string return types.
Unit Test Failure Example:
FAILURES!
Tests: 11683, Assertions: 20300, Failures: 1.
There was 1 failure:
1) TYPO3\CMS\IndexedSearch\Tests\Unit\IndexerTest::bodyDescriptionReturnsEmptyStringWhenMaxLengthIsZero
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-''
+'This content should not appear in description'Fix: Test logic is wrong - review the test expectations.
Step 4: Fix ALL Issues
⚠️ CRITICAL: A CI pipeline may have multiple failing jobs. Fix ALL of them:
# Example: 5 jobs failed (cgl, phpstan, 3x unit tests)
# You must fix:
# 1. Code style issues (single quotes)
# 2. PHPStan warnings (remove redundant assertions)
# 3. Unit test failures (fix test logic)
# Make all fixes
vim typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
# Stage changes
git add typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
# Amend commit
git commit --amend --no-edit
# Push updated patchset
git push origin HEAD:refs/for/mainStep 5: Wait for Re-verification
After pushing fixes: 1. CI automatically runs again 2. Old failed votes (Verified-1) are removed 3. Wait for all jobs to complete 4. Verify ALL jobs are now passing (green ✅)
Step 6: Mark as Ready for Review
Once ALL CI jobs pass: 1. Open your review on Gerrit 2. Click "More" → "Start Review" 3. Optionally add a comment: "Ready for review. All CI checks passing." 4. Your patch is now visible to core team reviewers
Common CI Failure Patterns
| Job Type | Common Issues | Where to Look |
|---|---|---|
| cgl (Code Style) | Double quotes, spacing, indentation | PHP CS Fixer diff in log |
| phpstan | Type errors, redundant code, undefined vars | Line numbers + error descriptions |
| unit tests | Test failures, assertion mismatches | Test name + expected vs actual |
| functional tests | Database issues, integration problems | Full stack trace in log |
If tests fail: 1. ⚠️ DO NOT GUESS - Always read actual job logs 2. Check ALL failing jobs, not just the first one 3. Access GitLab CI job logs via links on Gerrit 4. Fix all issues in one patchset 5. Push updated patchset (next section) 6. Wait for re-verification 7. Mark as ready only when ALL jobs pass
Updating Your Patch
When reviewers request changes or tests fail:
Step 1: Make Changes Locally
# Make code changes
vim path/to/file.php
# Stage changes
git add path/to/file.phpStep 2: Amend Commit
# Amend existing commit (DO NOT create new commit!)
git commit --amend
# CRITICAL: Keep the Change-Id line unchanged!Preserving Reviewer Changes
IMPORTANT: Reviewers often edit your commit message (adding backticks, formatting, Releases: line). Always preserve these changes when amending!
Safe Workflow:
# 1. Fetch the latest patchset FIRST
git fetch origin refs/changes/XX/NNNNN/N # N = latest patchset number
# Example: git fetch origin refs/changes/20/92020/3
# 2. Check reviewer's message
git log -1 --format="%B" FETCH_HEAD
# 3. Reset to reviewer's version (preserves their message)
git reset --soft FETCH_HEAD
# 4. Make your code changes
git add path/to/changed/file.php
# 5. Amend with reviewer's message preserved
git commit --amendCommon Reviewer Edits to Preserve:
- Backticks around code: `
$variable,ClassName` Releases: main, 14.0, 13.4line- "Added tests fixate this behavior." when tests are included
- Formatting improvements to description
Anti-pattern (loses reviewer changes):
# DON'T do this after reviewer edited your message!
git commit --amend --no-edit # ❌ Reverts to YOUR old messageStep 3: Push Updated Patchset
# Push to same Gerrit change
git push origin HEAD:refs/for/mainWhat happens:
- Gerrit matches Change-Id
- Creates new patchset (Patch Set 2, 3, etc.)
- Previous patchsets remain for comparison
- CI tests run again
Patchset Versioning:
- Patch Set 1: Initial submission
- Patch Set 2: First update
- Patch Set 3: Second update
- etc.
Rebasing Your Patch
Why Rebase?
While you're working, other contributors' patches get merged. Your patch becomes based on outdated code. Rebasing updates your patch to build on the latest codebase.
When to Rebase
- Merge conflict indicator appears on Gerrit
- Regularly during development (best practice)
- Before running tests
- When requested by reviewers
Method 1: Browser-Based Rebase (Easiest)
Requirements: No merge conflicts
Steps: 1. Open your patch on Gerrit 2. Click Rebase button (top right) 3. Select "Rebase on top of the main branch" 4. Click Rebase
Gerrit automatically:
- Rebases your change
- Creates new patchset
- Runs CI tests
Method 2: Command-Line Rebase
When to use: Merge conflicts exist, or prefer manual control
Steps:
# Ensure on your feature branch
git checkout feature/105737-fix-indexed-search
# Fetch latest changes
git fetch origin
# Rebase onto main
git rebase origin/mainIf no conflicts:
# Push rebased patch
git push origin HEAD:refs/for/mainIf conflicts occur: See Resolving Merge Conflicts section below.
Alternative Rebase Methods
Option A: Pull with Rebase
git pull --rebase origin mainOption B: Interactive Rebase (advanced)
git rebase -i origin/mainResolving Merge Conflicts
What Are Conflicts?
Conflicts occur when:
- You modified file X
- Someone else modified same lines in file X
- Their patch merged first
- Git can't auto-merge
Conflict Resolution Process
Step 1: Start Rebase
git rebase origin/mainOutput with conflicts:
CONFLICT (content): Merge conflict in path/to/file.php
error: could not apply abc123... Your commit message
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".Step 2: Identify Conflicted Files
git statusOutput:
On branch feature/105737-fix-indexed-search
You are currently rebasing branch 'feature/105737-fix-indexed-search' on 'abc123'.
(fix conflicts and then run "git rebase --continue")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: path/to/file.phpStep 3: Resolve Conflicts
Open conflicted file in editor:
<<<<<<< HEAD
// Code from main branch (their changes)
$result = newFunction($data);
=======
// Your changes
$result = oldFunction($data);
>>>>>>> Your commit messageChoose resolution:
Option A: Keep their changes
$result = newFunction($data);Option B: Keep your changes
$result = oldFunction($data);Option C: Merge both (most common)
// Updated to use new function while preserving your logic
$result = newFunction($processedData);Remove conflict markers (<<<<<<<, =======, >>>>>>>).
Step 4: Mark as Resolved
# Stage resolved files
git add path/to/file.php
# Check all conflicts resolved
git statusStep 5: Continue Rebase
git rebase --continueIf more conflicts exist, repeat steps 3-5.
Step 6: Push Rebased Patch
git push origin HEAD:refs/for/mainConflict Resolution Tips
Understand Context:
- Review their changes:
git show HEAD:path/to/file.php - Review your changes:
git show feature/105737-fix-indexed-search:path/to/file.php - Check file history:
git log -- path/to/file.php
Test After Resolution:
# Run tests locally
composer test:unit
composer test:functional
# Check syntax
php -l path/to/file.phpAsk for Help:
- Post in #typo3-cms-coredev Slack
- Comment on Gerrit review
- Reference conflicting patch if known
Aborting Rebase
If rebase goes wrong:
git rebase --abortReturns to pre-rebase state. You can try again.
Review Process
Voting System
Code Review (CR):
- +2: Looks good, approved
- +1: Looks mostly good
- 0: Neutral (default)
- -1: Needs improvement
- -2: Do not merge (veto)
Verified (V):
- +1: Tests passed
- 0: Not yet tested
- -1: Tests failed
Merge Requirements:
- At least +2 Code Review from core team member
- At least +1 Verified (CI tests passed)
- No unresolved -2 votes
- At least 2 reviewers involved (one must be core team)
Typical Review Timeline
Simple Bugfixes: 1-3 days Medium Features: 3-7 days Complex Features: 1-2 weeks Breaking Changes: 2-4 weeks (more scrutiny)
Factors affecting timeline:
- Code quality and completeness
- Test coverage
- Documentation
- Reviewer availability (volunteers!)
- Complexity and impact
Responding to Review Comments
Step 1: Read Feedback Carefully
- Understand what's being requested
- Ask questions if unclear
- Check if feedback applies to multiple locations
Step 2: Implement Changes
# Make requested changes
vim path/to/file.php
# Stage and amend
git add path/to/file.php
git commit --amend
# Push update
git push origin HEAD:refs/for/mainStep 3: Respond on Gerrit
- Click Reply button
- Address each comment:
- "Done" - Simple confirmation
- "Fixed in PS3" - Reference patchset number
- Explain your approach if different from suggestion
- Thank reviewers
- Click Send
Example Response
Thanks for the review!
> Line 45: Consider using dependency injection
Good point! I've refactored to use DI in PS3.
> Line 120: Add type hint
Added in PS3. Also added return type hints throughout.
> Missing tests for edge case
Added test case for empty string input in Tests/Unit/IndexerTest.phpGetting More Reviews
If no reviews after 3-4 days:
1. Advertise on Slack (#typo3-cms-coredev):
I've submitted a patch for #105737 (indexed search crash).
Would appreciate reviews when you have time: https://review.typo3.org/c/Packages/TYPO3.CMS/+/123452. Check patch quality:
- Tests passing?
- Documentation complete?
- Follows coding standards?
- Clear commit message?
3. Ask specific reviewers (if appropriate):
- Maintainers of affected area
- Previous contributors to same files
- Don't spam or DM randomly!
Gerrit Interface Guide
Review Page Sections
Header:
- Status (Active, Merged, Abandoned)
- Subject and description
- Owner and reviewers
- CI test results
Files:
- List of changed files
- Click file to see diff
- Add inline comments
History:
- Patchset versions
- Comments and votes
- CI results per patchset
Related Changes:
- Depends on / Needed by
- Related topics
- Conflicts with
Useful Gerrit Features
Diff Views:
- Side-by-side: Compare old/new code
- Unified: Traditional diff format
- Between patchsets: Compare PS1 vs PS2
Search:
- Find your changes:
owner:self status:open - Find by issue:
bug:105737 - Find by topic:
topic:indexed-search
Keyboard Shortcuts:
?: Show all shortcutsu: Go up to dashboarda: Expand all inline commentsc: Compose review commentn/p: Next/previous file
Advanced Topics
Cherry-Picking Patches
Apply someone else's patch locally:
# From Gerrit download dropdown, copy cherry-pick command
git fetch origin refs/changes/45/12345/3 && git cherry-pick FETCH_HEADTopics
Group related changes:
git push origin HEAD:refs/for/main%topic=indexed-search-improvementsWork In Progress (WIP)
Mark patch as work-in-progress:
git push origin HEAD:refs/for/main%wipOr on Gerrit web UI: More → Mark as Work In Progress
Use WIP when:
- Patch incomplete, not ready for review
- Want CI test results before review
- Demonstrating proof of concept
Remove WIP: More → Start Review
Private Changes
Keep change private (visible only to you and explicit reviewers):
git push origin HEAD:refs/for/main%privateDraft Comments
Save review comments without publishing: 1. Add comments on files 2. Click Save instead of Send 3. Edit later 4. Publish when ready
Troubleshooting
"Change-Id not found"
Problem: Missing or modified Change-Id
Solution:
# Ensure commit-msg hook installed
ls -la .git/hooks/commit-msg
# If missing, install
composer gerrit:setup
# Amend commit to generate Change-Id
git commit --amend --no-edit"Prohibited by Gerrit"
Problem: Pushing to wrong branch or permissions issue
Solution:
# Verify push URL
git config remote.origin.pushurl
# Should be: ssh://<username>@review.typo3.org:29418/Packages/TYPO3.CMS.git
# Push to refs/for/main, not main directly
git push origin HEAD:refs/for/main"No New Changes"
Problem: Pushing identical commit
Solution:
- Make actual code changes
- Or amend commit message
- Then push again
Multiple Commits on Branch
Problem: Accidentally created multiple commits
Solution: Squash into one commit
# Interactive rebase
git rebase -i origin/main
# In editor, change all but first "pick" to "squash"
# Save and exit
# Edit combined commit message
# PushBest Practices
1. One commit per patch: Squash multiple commits into one 2. Rebase regularly: Stay up-to-date with main branch 3. Preserve Change-Id: Never modify when amending 4. Respond promptly: Reply to reviews within 2-3 days 5. Test locally first: Run tests before pushing 6. Clear communication: Explain changes in Gerrit comments 7. Be patient: Reviewers are volunteers 8. Learn from feedback: Apply lessons to future patches
Resources
- Gerrit: https://review.typo3.org
- Forger Search: https://forger.typo3.com
- Gerrit Documentation: https://review.typo3.org/Documentation/
- Slack: #typo3-cms-coredev
Quick Command Reference
| Action | Command |
|---|---|
| Push new patch | git push origin HEAD:refs/for/main |
| Update patch | git commit --amend && git push origin HEAD:refs/for/main |
| Rebase on main | git fetch origin && git rebase origin/main |
| Abort rebase | git rebase --abort |
| Continue rebase | git rebase --continue |
| Cherry-pick | git fetch origin refs/changes/XX/XXXX/X && git cherry-pick FETCH_HEAD |
| Push as WIP | git push origin HEAD:refs/for/main%wip |
| Test SSH | ssh -p 29418 <user>@review.typo3.org |
Modern TYPO3 Core Patterns
Architectural patterns and best practices for TYPO3 Core contributions in v13+.
PHP 8 Attributes for Auto-Discovery
TYPO3 v13+ uses PHP 8 Attributes for automatic class discovery and registration. This replaces manual configuration arrays and makes code more self-documenting.
Pattern: Attribute-Based Registration
Creating a Discovery Attribute:
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
/**
* Attribute to mark a class for automatic discovery.
*
* @internal This class is only meant to be used within EXT:install
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
final class ExtensionScannerMatcher
{
public function __construct(
public readonly ?string $configurationFile = null
) {}
}Using the Attribute:
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
#[ExtensionScannerMatcher]
class ArrayDimensionMatcher extends AbstractCoreMatcher
{
// Implementation...
}
// With custom configuration file
#[ExtensionScannerMatcher('CustomConfig.php')]
class SpecialMatcher extends AbstractCoreMatcher
{
// Implementation...
}Registry for Discovery:
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Install\ExtensionScanner\Php;
class MatcherRegistry
{
private ?array $matcherClasses = null;
public function getMatcherClasses(): array
{
if ($this->matcherClasses !== null) {
return $this->matcherClasses;
}
$this->discoverMatchers();
return $this->matcherClasses;
}
private function discoverMatchers(): void
{
$this->matcherClasses = [];
$matcherDirectory = GeneralUtility::getFileAbsFileName('EXT:install/Classes/.../Matcher/');
foreach (glob($matcherDirectory . '*.php') as $phpFile) {
$className = 'TYPO3\\CMS\\Install\\...' . pathinfo($phpFile, PATHINFO_FILENAME);
if (!class_exists($className)) {
continue;
}
$reflection = new \ReflectionClass($className);
// Skip abstract classes
if ($reflection->isAbstract()) {
continue;
}
// Check for attribute
$attributes = $reflection->getAttributes(ExtensionScannerMatcher::class);
if (empty($attributes)) {
continue;
}
$this->matcherClasses[] = $className;
}
}
}TYPO3's registerAttributeForAutoconfiguration Pattern
For DI-integrated auto-discovery, TYPO3 uses registerAttributeForAutoconfiguration() in Configuration/Services.php:
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Core;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) {
// Register attribute for auto-tagging
$containerBuilder->registerAttributeForAutoconfiguration(
Attribute\AsEventListener::class,
static function (ChildDefinition $definition, AsEventListener $attribute): void {
$definition->addTag(AsEventListener::TAG_NAME, [
'identifier' => $attribute->identifier,
'event' => $attribute->event,
'method' => $attribute->method,
'before' => $attribute->before,
'after' => $attribute->after,
]);
}
);
};Attribute with TAG_NAME constant:
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Core\Attribute;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AsEventListener
{
public const TAG_NAME = 'event.listener';
public function __construct(
public readonly ?string $identifier = null,
public readonly ?string $event = null,
public readonly ?string $method = null,
public readonly ?string $before = null,
public readonly ?string $after = null,
) {}
}When to Use Each Pattern
| Pattern | Use Case | DI Integration |
|---|---|---|
| Simple Attribute + Registry | Internal discovery, no DI needed | No |
| registerAttributeForAutoconfiguration | Public API, DI services | Yes |
Real TYPO3 Core Examples:
AsEventListener- Event listener registrationAsController- Backend controller registrationUpgradeWizard- Upgrade wizard discoveryAsSchedulableTask- Scheduler task registration
Error Handling Patterns
Silent Failure with Graceful Degradation
When loading external configuration that might be malformed:
// Load configuration, skip matcher if config file is malformed
try {
$configuration = require $configPath;
} catch (\Throwable) {
// Configuration file has syntax errors or other issues
// Skip this matcher rather than crashing the entire scanner
continue;
}
// Only add to registry after successful load
$this->matcherClasses[] = $className;
$this->matcherConfigurations[$className] = $configuration;Trade-off Consideration:
- Silent failure: System continues working, but debugging is harder
- Recommendation from reviewers: Add
trigger_error()or logging:
try {
$configuration = require $configPath;
} catch (\Throwable $e) {
trigger_error(
sprintf('Failed to load matcher configuration %s: %s', $configPath, $e->getMessage()),
E_USER_WARNING
);
continue;
}Memoization Pattern
Cache expensive operations in nullable properties:
class MatcherRegistry
{
private ?array $matcherClasses = null;
private ?array $matcherConfigurations = null;
public function getMatcherClasses(): array
{
if ($this->matcherClasses !== null) {
return $this->matcherClasses;
}
$this->discoverMatchers();
return $this->matcherClasses;
}
public function getMatcherConfigurations(): array
{
if ($this->matcherConfigurations !== null) {
return $this->matcherConfigurations;
}
$this->discoverMatchers();
return $this->matcherConfigurations;
}
private function discoverMatchers(): void
{
// Populate both arrays in single discovery pass
$this->matcherClasses = [];
$this->matcherConfigurations = [];
// ... discovery logic
}
}Benefits:
- Lazy initialization (only compute when needed)
- Single computation even with multiple calls
- Atomic population (both arrays filled together)
Convention over Configuration
File naming conventions:
- Matcher class
ArrayDimensionMatcher→ Config fileArrayDimensionMatcher.php - Override via attribute:
#[ExtensionScannerMatcher('CustomConfig.php')]
// Determine configuration file name
$configFile = $attribute->configurationFile ?? $reflection->getShortName() . '.php';
$configPath = $configurationDirectory . $configFile;PHPStan Level 9+ Compatibility
Test-Specific Ignore Patterns
When tests validate runtime behavior that PHPStan already knows from types:
#[Test]
public function allDiscoveredMatcherClassesExtendAbstractCoreMatcher(): void
{
$subject = new MatcherRegistry();
$result = $subject->getMatcherClasses();
foreach ($result as $matcherClass) {
// @phpstan-ignore staticMethod.alreadyNarrowedType
self::assertTrue(
is_subclass_of($matcherClass, AbstractCoreMatcher::class), // @phpstan-ignore function.alreadyNarrowedType
sprintf('%s should extend AbstractCoreMatcher', $matcherClass)
);
}
}Common identifiers:
staticMethod.alreadyNarrowedType- For assertTrue/assertFalse/assertIsArrayfunction.alreadyNarrowedType- For is_subclass_of/is_array/is_string
Code Review Insights
From real TYPO3 Core review feedback:
Multi-Model Consensus Findings
Strengths typically approved:
- Modern PHP 8 patterns (attributes, named arguments, constructor promotion)
- Strong typing with
declare(strict_types=1) - Memoization for expensive operations
- Convention-over-configuration approaches
Common review concerns: 1. Silent failures - Reviewers prefer logging/trigger_error over silent catch 2. Test brittleness - assertCount(22) breaks when matchers added; prefer assertGreaterThan(0) 3. Flat directory assumption - Document if subdirectories aren't supported 4. DI bypass - new $class() prevents future service injection
Reviewer Expectations
- Architectural alignment: Use framework patterns, don't reinvent
- Study existing code: Match patterns in affected areas
- Multiple revisions normal: 7-24 patch sets common for complex features
- Documentation: Comment non-obvious design decisions
Resources
TYPO3 Contribution Troubleshooting Guide
Common issues and solutions for TYPO3 Core contributions.
Git Configuration Issues
Problem: "Permission denied (publickey)"
Symptoms:
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.Causes:
- SSH keys not configured
- Wrong SSH key being used
- Key not added to Gerrit/GitHub
Solutions:
Check SSH keys exist:
ls -la ~/.ssh/
# Look for id_ed25519 or id_rsa filesGenerate new SSH key if needed:
ssh-keygen -t ed25519 -C "your-email@example.org"Add key to SSH agent:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519Test connection:
# Test GitHub
ssh -T git@github.com
# Test Gerrit
ssh -p 29418 <username>@review.typo3.orgVerify key on Gerrit: 1. Visit https://review.typo3.org/settings/#SSHKeys (NOT my.typo3.org!) 2. Click "Add new SSH key" 3. Paste your public key: cat ~/.ssh/id_ed25519.pub 4. Ensure your public key is listed
Configure SSH for Gerrit (add to ~/.ssh/config):
Host review.typo3.org
User your-typo3-username
IdentityFile ~/.ssh/id_ed25519
Port 29418Update Git remote URL to include your username:
cd /path/to/typo3
git remote set-url origin ssh://your-typo3-username@review.typo3.org:29418/Packages/TYPO3.CMS
# Or add as separate remote
git remote add gerrit ssh://your-typo3-username@review.typo3.org:29418/Packages/TYPO3.CMSProblem: "fatal: refusing to merge unrelated histories"
Symptoms:
fatal: refusing to merge unrelated historiesCause: Trying to merge branches with no common ancestor
Solution:
# Only if you're absolutely sure you want to merge
git pull origin main --allow-unrelated-histories
# Better: Start fresh clone
cd ..
git clone git@github.com:typo3/typo3.git typo3-newProblem: "email address is not registered in your account"
Symptoms:
remote: ERROR: commit abc123: email address user@example.com is not registered
in your account, and you lack 'forge committer' permission.
remote: The following addresses are currently registered:
remote: other@example.comCause: Git commit email doesn't match any email registered in your Gerrit account
IMPORTANT: Git commits have TWO email addresses:
- Author: Who wrote the code (shown in
git log) - Committer: Who committed the code (used by Gerrit for permission checks)
Gerrit checks the committer email, not the author email. You can keep a different author email if needed.
Solution:
1. Check your Gerrit registered emails: Visit: https://review.typo3.org/settings#EmailAddresses
2. Update Git configuration to match (for future commits):
# Use one of your registered emails
git config user.email "your-registered@email.com"
# If working in TYPO3 repo, update local config
cd /path/to/typo3
git config user.email "your-registered@email.com"3. Fix existing commit - choose one option:
Option A: Change both author and committer:
git commit --amend --reset-author --no-editOption B: Change only committer (keep original author):
GIT_COMMITTER_NAME="Your Name" \
GIT_COMMITTER_EMAIL="your-registered@email.com" \
git commit --amend --no-editThis is useful when you want to preserve the original author (e.g., info@yourdomain.de) but need to use a different registered email for the committer.
4. Push again:
git push origin HEAD:refs/for/mainPrevention: Always verify your Git email matches Gerrit before making commits. Run verify-prerequisites.sh script before starting work.
Problem: "Your branch is ahead of 'origin/main' by X commits"
Symptoms:
Your branch is ahead of 'origin/main' by 5 commits.
(use "git push" to publish your local commits)Cause: You have commits not yet pushed
Solutions:
If commits should be separate patches:
# Push each commit separately
git push origin <commit-hash>:refs/for/mainIf commits should be one patch (most common):
# Squash into single commit
git rebase -i origin/main
# Change all but first "pick" to "squash"
# Edit commit message
git push origin HEAD:refs/for/mainProblem: "Branch 'main' set up to track remote branch 'main' from 'origin'"
Symptoms: Can't push, tracking wrong remote
Solution:
# Reset remote configuration
git config remote.origin.pushurl ssh://<USERNAME>@review.typo3.org:29418/Packages/TYPO3.CMS.git
git config remote.origin.push +refs/heads/main:refs/for/mainGerrit Issues
Problem: "Change-Id not found in commit message footer"
Symptoms:
remote: ERROR: commit abc123: missing Change-Id in message footerCause: commit-msg hook not installed or bypassed
Solutions:
Install hook (choose one method):
# Method 1: Via composer (if available)
composer gerrit:setup
# Method 2: Via curl (recommended - always gets latest version)
curl -o "$(git rev-parse --git-dir)/hooks/commit-msg" \
https://review.typo3.org/tools/hooks/commit-msg && \
chmod +x "$(git rev-parse --git-dir)/hooks/commit-msg"
# Method 3: Copy from Build directory (if exists)
cp Build/git-hooks/commit-msg .git/hooks/
chmod +x .git/hooks/commit-msgFix existing commit:
# Amend to trigger hook
git commit --amend --no-edit
# Verify Change-Id added
git log -1If Change-Id still missing:
# Manually run hook
.git/hooks/commit-msg .git/COMMIT_EDITMSG
# Amend with new message
git commit --amend -F .git/COMMIT_EDITMSGProblem: "Prohibited by Gerrit: not permitted to upload"
Symptoms:
remote: ERROR: [a1b2c3] missing Change-Id in message footer
remote: ERROR: Prohibited by Gerrit: not permitted to uploadCauses:
- Pushing to wrong ref
- Account permissions issue
- SSH key not configured
Solutions:
Check push URL:
git config remote.origin.pushurl
# Should be: ssh://<USERNAME>@review.typo3.org:29418/Packages/TYPO3.CMS.gitPush to correct ref:
# Push to refs/for/main, NOT directly to main
git push origin HEAD:refs/for/mainVerify SSH connection:
ssh -p 29418 <USERNAME>@review.typo3.orgCheck username:
git config remote.origin.pushurl
# Ensure <USERNAME> matches your Gerrit usernameProblem: "Invalid Change-Id"
Symptoms: Gerrit rejects Change-Id format
Cause: Manually created or corrupted Change-Id
Solution:
# Remove invalid Change-Id from commit message
git commit --amend
# Delete the Change-Id line
# Save and exit
# Re-amend to generate new Change-Id
git commit --amend --no-edit
# Verify format: "Change-Id: I" followed by 40 hex characters
git log -1 | grep Change-IdProblem: "New Patchset Not Appearing"
Symptoms: Push succeeds but no new patchset on Gerrit
Causes:
- No actual changes (identical commit)
- Change-Id modified (created new review instead)
Solutions:
Check if new review created:
- Look for different review URL in push output
- Search Gerrit for your recent changes
Ensure actual changes:
# View last commit changes
git show HEAD
# Compare with Gerrit patchset
git fetch origin refs/changes/XX/XXXX/X
git diff FETCH_HEADIf Change-Id was modified:
# Get original Change-Id from Gerrit
# Edit commit message to restore it
git commit --amend
# Restore original Change-Id
# Save and pushRebase and Merge Issues
Problem: "CONFLICT (content): Merge conflict in file.php"
Symptoms:
CONFLICT (content): Merge conflict in path/to/file.php
Automatic merge failed; fix conflicts and then commit the result.Solution: See "Resolving Merge Conflicts" in gerrit-workflow.md
Quick steps:
# 1. Open conflicted file, resolve conflicts
vim path/to/file.php
# 2. Remove conflict markers (<<<<<<<, =======, >>>>>>>)
# 3. Stage resolved file
git add path/to/file.php
# 4. Continue rebase
git rebase --continue
# 5. Push updated patch
git push origin HEAD:refs/for/mainProblem: "Cannot rebase: You have unstaged changes"
Symptoms:
error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.Solutions:
Option A: Stash changes:
git stash
git rebase origin/main
git stash popOption B: Commit changes:
git add .
git commit --amend
git rebase origin/mainOption C: Discard changes (if unwanted):
git checkout -- .
git rebase origin/mainProblem: "Already up to date"
Symptoms: Rebase says "up to date" but Gerrit shows conflicts
Cause: Rebasing wrong branch or remote not updated
Solutions:
# Fetch latest changes first
git fetch origin
# Ensure on correct branch
git branch
# Should show * feature/your-branch
# Try rebase again
git rebase origin/mainTest Failures
Problem: "Bamboo CI Tests Failing"
Symptoms: Red X on Gerrit review, tests failed
Solutions:
View test results: 1. Click test result on Gerrit 2. Read error messages 3. Identify failing tests
Run tests locally:
# Unit tests
composer test:unit
# Specific test class
composer test:unit -- path/to/TestClass.php
# Functional tests
composer test:functional
# All tests
composer testCommon test failures:
PHP syntax error:
php -l path/to/file.phpCoding standards:
composer cs:check
composer cs:fixMissing dependencies:
composer installFix and resubmit:
# After fixing
git add .
git commit --amend
git push origin HEAD:refs/for/mainProblem: "Tests Pass Locally But Fail on CI"
Causes:
- PHP version differences
- Missing dependencies
- Environment-specific issues
Solutions:
Check PHP version:
# Local version
php -v
# CI uses multiple versions (7.4, 8.0, 8.1, 8.2)
# Ensure code compatible with allTest multiple PHP versions locally:
# Using Docker
docker run --rm -v $(pwd):/app php:8.2-cli composer test
docker run --rm -v $(pwd):/app php:8.1-cli composer testCheck CI logs carefully:
- Look for deprecation warnings
- Check for missing extensions
- Verify database-specific issues
Development Environment Issues
Problem: "Composer command not found"
Solution:
# Install Composer globally
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# Or use specific version
php composer.phar <command>Problem: "DDEV not starting"
Solutions:
Check Docker running:
docker psRestart DDEV:
ddev restartReset DDEV:
ddev stop
ddev clean
ddev startCheck logs:
ddev logsProblem: "Out of memory" during Composer operations
Solution:
# Increase PHP memory limit
php -d memory_limit=2G /usr/local/bin/composer install
# Or set in php.ini
memory_limit = 2GCommit Message Issues
Problem: "Subject line too long"
Symptoms: Validation fails, >72 characters
Solution:
# Amend commit
git commit --amend
# Shorten subject line to ≤52 chars (recommended)
# Or ≤72 chars (absolute max)Problem: "Wrong commit type"
Example: Used [FEATURE] on bugfix
Solution:
# Amend commit
git commit --amend
# Change [FEATURE] to [BUGFIX]
# Save and exitProblem: "Missing footer tags"
Symptoms: No Resolves or Releases tags
Solution:
# Amend commit
git commit --amend
# Add required footer:
# Resolves: #12345
# Releases: main, 13.4
# (Keep Change-Id unchanged!)Account Issues
Problem: "Can't access Gerrit"
Solutions:
Verify TYPO3.org account:
- Visit https://my.typo3.org
- Confirm account active
Sign in to Gerrit:
- Visit https://review.typo3.org
- Click "Sign In"
- Use TYPO3.org credentials
Clear browser cache:
- Cookies might be stale
- Try incognito/private mode
Problem: "SSH timeout connecting to Gerrit"
Symptoms:
ssh: connect to host review.typo3.org port 29418: Operation timed outCauses:
- Firewall blocking port 29418
- Network restrictions
- Corporate VPN issues
Solutions:
Try different network:
- Use mobile hotspot
- Try from different location
Check firewall:
telnet review.typo3.org 29418Use HTTPS instead (less common for TYPO3):
git config remote.origin.url https://review.typo3.org/Packages/TYPO3.CMSGetting Help
Where to Ask
Slack: #typo3-cms-coredev
@here I'm having trouble with [issue]. I've tried [solutions].
Error message: [paste error]
Gerrit review: https://review.typo3.org/c/...Forge: https://forge.typo3.org
- Create issue if you found a bug in contribution process
Documentation: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
What to Include When Asking
1. What you're trying to do 2. What you've tried 3. Error messages (full text) 4. Links (Gerrit review, Forge issue) 5. Environment (OS, PHP version, Git version) 6. Relevant commands and their output
Example Help Request
I'm trying to push my patch for #105737 but getting this error:
remote: ERROR: missing Change-Id in message footer
I've tried:
- Running composer gerrit:setup
- Checking that .git/hooks/commit-msg exists and is executable
- Running git commit --amend --no-edit
My setup:
- macOS 12.6
- Git 2.37.1
- PHP 8.1.12
Review (if already created): https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345
Any ideas what I'm missing?Preventive Measures
Before Starting
- [ ] Run
scripts/verify-prerequisites.sh - [ ] Verify SSH access to Gerrit
- [ ] Check git configuration
- [ ] Ensure hooks installed
Before Committing
- [ ] Run tests locally
- [ ] Check coding standards
- [ ] Validate commit message format
- [ ] Review changes with
git diff
Before Pushing
- [ ] Rebase on latest main
- [ ] Ensure single commit
- [ ] Verify Change-Id present
- [ ] Check no
composer.lockchanges:git diff --cached --name-only | grep composer.lock - [ ] Test one more time
After Pushing
- [ ] Verify patch on Gerrit
- [ ] Watch CI test results
- [ ] Respond to initial feedback
- [ ] Update if tests fail
CI/GitLab Issues
Problem: "How do I find ALL failing CI jobs?"
Critical: Never assume what failed - always check ALL job logs!
Step-by-Step Process:
1. Open your Gerrit review:
https://review.typo3.org/c/Packages/TYPO3.CMS/+/YOUR_NUMBER2. Find CI Results section:
- Scroll down to see GitLab CI results
- Look for red ❌ marks next to job names
- Note ALL failing job names (there may be multiple!)
3. For each failing job:
- Click the job name/link
- You'll be redirected to GitLab:
https://git.typo3.org/typo3/CI/cms/-/jobs/XXXXXX - Click "Show complete raw" or append
/rawto URL - Read the ACTUAL error messages
Common Failing Jobs:
cgl pre-merge- Code style violationsphpstan php X.X pre-merge- Static analysis errorsunit php X.X pre-merge- Unit test failures (may fail on multiple PHP versions)functional php X.X pre-merge- Functional test failures
Problem: "Code Style (cgl) Failed"
Symptoms:
1) path/to/File.php (single_quote)
---------- begin diff ----------
- body: "some string"
+ body: 'some string'
----------- end diff -----------Cause: Code doesn't match TYPO3 coding standards (PSR-12 + TYPO3 rules)
Common Issues:
- Double quotes instead of single quotes for simple strings
- Wrong indentation (spaces vs tabs)
- Missing/extra spaces
- Line length violations
Solutions:
Option 1: Auto-fix with PHP CS Fixer (recommended):
# Install PHP CS Fixer if not available
composer require --dev friendsofphp/php-cs-fixer
# Run fixer on specific file
./Build/Scripts/cglFixMyCommit.sh
# Or manually on specific files
vendor/bin/php-cs-fixer fix path/to/File.phpOption 2: Manual fix:
# Read the diff carefully
# Fix issues manually in your editor
vim path/to/File.php
# Amend and push
git add path/to/File.php
git commit --amend --no-edit
git push origin HEAD:refs/for/mainProblem: "PHPStan Failed"
Symptoms:
------ ---------------------------------------------------------------------
Line path/to/File.php
------ ---------------------------------------------------------------------
236 Call to static method Assert::assertNotNull()
with string will always evaluate to true.
------ ---------------------------------------------------------------------Cause: Static analysis detected potential bugs or redundant code
Common Issues:
- Redundant type checks (asserting non-null on typed return values)
- Undefined variables
- Type mismatches
- Incorrect PHPDoc annotations
Solutions:
# Run PHPStan locally to see all issues
./Build/Scripts/runTests.sh -s phpstan
# Fix the issues:
# - Remove redundant assertions
# - Add proper type hints
# - Fix type mismatches
# - Update PHPDoc blocks
# Example: Remove redundant assertNotNull()
# Before:
$result = $subject->bodyDescription($dto); // returns string
self::assertNotNull($result); // WRONG - string can't be null
# After:
$result = $subject->bodyDescription($dto);
self::assertNotEmpty($result); // CORRECT - checks string is not empty
# Amend and push
git add path/to/File.php
git commit --amend --no-edit
git push origin HEAD:refs/for/mainProblem: "Unit Tests Failed"
Symptoms:
FAILURES!
Tests: 11683, Assertions: 20300, Failures: 1.
1) Vendor\Package\Tests\Unit\ClassTest::testMethod
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-''
+'unexpected content'Cause: Test assertions don't match actual behavior
Solutions:
1. Read the full error:
# Get the full test output from GitLab job log
# Look for:
# - Which test failed (full class name and method)
# - What was expected vs actual
# - Any exception messages or stack traces2. Run test locally:
# Run specific test
./Build/Scripts/runTests.sh -s unit path/to/Tests/Unit/ClassTest.php::testMethod
# Or run all tests in file
./Build/Scripts/runTests.sh -s unit path/to/Tests/Unit/ClassTest.php3. Fix the issue:
- If test logic is wrong: Update test assertions
- If implementation is wrong: Fix the implementation
- If test is no longer valid: Remove it (with good reason!)
4. Verify and push:
# Run tests locally to confirm fix
./Build/Scripts/runTests.sh -s unit
# Amend and push
git add .
git commit --amend --no-edit
git push origin HEAD:refs/for/mainProblem: "Multiple jobs failed - which do I fix first?"
Answer: Fix ALL of them in ONE patchset!
Process:
# 1. List all failures
# Example: cgl, phpstan, 3x unit tests all failed
# 2. Read ALL job logs
# - cgl: Double quotes issue
# - phpstan: Redundant assertion
# - unit tests: Test logic error
# 3. Fix all issues locally
vim Tests/Unit/IndexerTest.php
# - Change double quotes to single quotes (cgl fix)
# - Remove assertNotNull() (phpstan fix)
# - Fix test logic (unit test fix)
# 4. Verify fixes locally
./Build/Scripts/cglFixMyCommit.sh
./Build/Scripts/runTests.sh -s phpstan
./Build/Scripts/runTests.sh -s unit
# 5. Commit and push ONCE
git add Tests/Unit/IndexerTest.php
git commit --amend --no-edit
git push origin HEAD:refs/for/main
# 6. Wait for re-verification
# All 5 jobs should now passProblem: "Patch is in WIP state - reviewers can't see it"
Symptoms:
- Patch shows [WIP] tag on Gerrit
- No review feedback after several days
- CI tests passed but no reviews
Cause: New patches are WIP by default - reviewers can't see them!
Solution:
1. Verify all CI jobs pass:
Go to Gerrit review page
Check all CI results are green ✅
If any red ❌, fix those first!2. Mark as ready for review:
Click "More" button (top right on Gerrit)
Click "Start Review"
Optionally add comment: "Ready for review. All CI checks passing."3. Advertise on Slack (optional, for visibility):
#typo3-cms-coredev channel:
"Submitted patch for #105737 - ready for review when you have time:
https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345"Problem: "CI takes forever to run"
Typical Duration: 10-20 minutes for full pipeline
If longer than 30 minutes:
- Check GitLab CI status page: https://git.typo3.org/typo3/CI/cms/-/pipelines
- Pipeline might be queued behind other patches
- Check Slack #typo3-cms-coredev for CI outages
- Be patient - pipelines run in order
Don't:
- Push multiple updates while CI is running
- Spam push repeatedly
- Rebase while CI is active
Do:
- Wait for current CI to finish
- Make all fixes in one commit
- Push once with all fixes
Common Patch Pitfalls
Problem: "composer.lock included in patch"
Symptoms:
- Gerrit shows
composer.lockin changed files - Reviewer mentions lock file shouldn't be changed
- Large diff with dependency changes
Cause: Running composer install or composer update and accidentally staging the lock file
Why This Matters:
- TYPO3 Core manages dependencies centrally
composer.lockchanges should NEVER be part of feature/bugfix patches- Lock file changes require separate, dedicated patches with proper review
Solution:
If already committed but not pushed:
# Remove composer.lock from the commit
git reset HEAD~ -- composer.lock
git checkout -- composer.lock
git commit --amend --no-editIf already pushed to Gerrit:
# Remove from current commit
git reset HEAD~ -- composer.lock
git checkout -- composer.lock
git commit --amend --no-edit
# Push new patchset
git push origin HEAD:refs/for/mainPrevention:
# Before committing, always check what's staged
git status
git diff --cached --name-only
# Only stage specific files
git add path/to/changed/files
# NOT: git add . (this catches everything including composer.lock)Problem: "patch was created without a push certificate"
Symptoms:
- Gerrit shows warning: "this patch was created without a push certificate"
- Yellow/orange indicator on patch
Background:
- Push certificates are cryptographic signatures proving who pushed the code
- They use GPG (GNU Privacy Guard), NOT SSH keys
- This is completely optional for TYPO3 contributions
- Many successful contributors never use push certificates
What Push Certificates Are:
Push certificates use GPG to sign the push operation itself (not the commit).
This proves that the person with access to the GPG key initiated the push.
It's an additional layer of verification beyond SSH authentication.Why You Might See This Warning:
- You don't have GPG configured (most common - totally fine!)
- GPG is configured but not linked to Git
- Your GPG key isn't uploaded to Gerrit
Solutions:
Option A: Ignore It (Recommended for most contributors):
- The warning is informational only
- Patches are accepted without push certificates
- SSH authentication is sufficient for contribution
Option B: Set Up GPG Signing (Optional):
# 1. Generate GPG key if you don't have one
gpg --full-generate-key
# 2. List your keys
gpg --list-secret-keys --keyid-format=long
# 3. Configure Git to use GPG
git config --global user.signingkey YOUR_KEY_ID
git config --global push.gpgSign true
# 4. Upload public key to Gerrit
# Go to: https://review.typo3.org/settings/#GPGKeys
# Paste output of: gpg --armor --export YOUR_KEY_IDKey Points:
- ❌ SSH keys do NOT create push certificates
- ✅ GPG keys are required for push certificates
- ✅ Push certificates are optional for TYPO3 contributions
- ✅ Patches are merged without push certificates regularly
Quick Diagnostic Commands
# Check git configuration
git config -l | grep -E "user\.|remote\.origin"
# Verify SSH connection
ssh -T -p 29418 <username>@review.typo3.org
# Check git hooks
ls -la .git/hooks/ | grep -E "commit-msg|pre-commit"
# View last commit
git log -1 --pretty=full
# Check remote configuration
git remote -v
# Verify Change-Id in last commit
git log -1 | grep Change-Id
# Check for unstaged changes
git status
# View diff of changes
git diff HEAD
# List all branches
git branch -aAdditional Resources
- Contribution Guide: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
- Gerrit Documentation: https://review.typo3.org/Documentation/
- Git Documentation: https://git-scm.com/doc
- Slack: https://typo3.slack.com (#typo3-cms-coredev)
- Forge: https://forge.typo3.org
---
Gerrit WIP State Management
Problem: "How do I mark my patch as ready for review?"
Background:
- All new patches start as WIP (Work in Progress) automatically
- Reviewers cannot see WIP patches
- You must mark patches as "ready" before reviewers can see them
Solution A - Command Line (Recommended):
Remove WIP state via git push with %ready flag:
# If no code changes needed, create empty patchset
git commit --amend --allow-empty --no-edit
git push origin HEAD:refs/for/main%readySolution B - Web UI:
1. Open your review: https://review.typo3.org/c/Packages/TYPO3.CMS/+/XXXXX 2. Click "Start Review" button (top right, near your avatar) 3. Done - patch now visible to reviewers
Solution C - Combined with Code Changes:
If you're pushing code fixes, add %ready to remove WIP at the same time:
git commit --amend
git push origin HEAD:refs/for/main%readySetting WIP State:
# Mark as WIP on initial push
git push origin HEAD:refs/for/main%wip
# Or set WIP via web UI: More menu → "Mark as Work in Progress"What DOESN'T Work:
# ❌ SSH 'gerrit review' command has NO WIP flags
ssh -p 29418 user@review.typo3.org gerrit review --ready 12345,1
ssh -p 29418 user@review.typo3.org gerrit review --wip 12345,1Key Points:
- ✅ Use
%wipand%readyflags withgit push - ✅ Empty pushes with
--allow-emptyare accepted - ❌ SSH
gerrit reviewcommand does NOT support WIP operations - ✅ Web UI works but command line is faster