fix: prevent benchmark/publish infinite loop with path filters#79
Merged
github-actions[bot] merged 1 commit intomainfrom Mar 26, 2026
Merged
fix: prevent benchmark/publish infinite loop with path filters#79github-actions[bot] merged 1 commit intomainfrom
github-actions[bot] merged 1 commit intomainfrom
Conversation
Three-layer defense against the benchmark→merge→re-trigger loop: 1. **Path filters** (primary, deterministic): - Benchmark workflow: only triggers on src/**, scripts/**, benchmarks.yml - Publish workflow: only triggers on src/** changes - README-only merges from benchmark PRs never match → no re-trigger 2. **Actor guard** (secondary safety net): - Both workflows keep `github.actor != 'github-actions[bot]'` 3. **Auto-merge uses GITHUB_TOKEN** (not PAT_TOKEN): - Merge actor is now 'github-actions[bot]' instead of PAT owner - Actor guards work correctly as secondary defense - PAT_TOKEN still used only for benchmark PR creation (required) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
📊 Benchmark Results
🔵 Flat Mapping — 10-property object
🟡 Flattening — 2 nested objects → 8 flat properties
🟣 Deep Mapping — 2 nested address objects
🟢 Complex Mapping — nested object + collection
🟠 Collection — 100-item
|
| Method | Mean | Error | StdDev | Min | Median | Max | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Manual | 1.731 μs | 0.6117 μs | 0.0335 μs | 1.700 μs | 1.727 μs | 1.767 μs | 1.00 | 0.02 | 1 | 0.5283 | 0.0172 | 8.65 KB | 1.00 |
| EggMapper | 1.754 μs | 0.2548 μs | 0.0140 μs | 1.740 μs | 1.753 μs | 1.768 μs | 1.01 | 0.02 | 1 | 0.5283 | 0.0172 | 8.65 KB | 1.00 |
| AutoMapper | 2.352 μs | 0.6798 μs | 0.0373 μs | 2.314 μs | 2.353 μs | 2.389 μs | 1.36 | 0.03 | 2 | 0.6065 | 0.0191 | 9.95 KB | 1.15 |
| Mapster | 1.756 μs | 0.2864 μs | 0.0157 μs | 1.740 μs | 1.758 μs | 1.771 μs | 1.01 | 0.02 | 1 | 0.5283 | 0.0172 | 8.65 KB | 1.00 |
| MapperlyMap | 1.819 μs | 0.2305 μs | 0.0126 μs | 1.808 μs | 1.816 μs | 1.833 μs | 1.05 | 0.02 | 1 | 0.5283 | 0.0172 | 8.65 KB | 1.00 |
| AgileMapper | 2.538 μs | 0.8063 μs | 0.0442 μs | 2.492 μs | 2.542 μs | 2.580 μs | 1.47 | 0.03 | 2 | 0.5417 | 0.0153 | 8.91 KB | 1.03 |
🟠 Collection — 100-item List<T>
| Method | Mean | Error | StdDev | Min | Median | Max | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Manual | 5.321 μs | 0.5789 μs | 0.0317 μs | 5.288 μs | 5.322 μs | 5.351 μs | 1.00 | 0.01 | 1 | 1.6708 | 0.0916 | 27.4 KB | 1.00 |
| EggMapper | 5.915 μs | 5.6239 μs | 0.3083 μs | 5.624 μs | 5.882 μs | 6.238 μs | 1.11 | 0.05 | 1 | 1.6708 | 0.0916 | 27.4 KB | 1.00 |
| AutoMapper | 6.712 μs | 2.4001 μs | 0.1316 μs | 6.617 μs | 6.658 μs | 6.862 μs | 1.26 | 0.02 | 1 | 1.7548 | 0.1068 | 28.7 KB | 1.05 |
| Mapster | 5.808 μs | 1.3360 μs | 0.0732 μs | 5.752 μs | 5.781 μs | 5.891 μs | 1.09 | 0.01 | 1 | 1.6708 | 0.0916 | 27.4 KB | 1.00 |
| MapperlyMap | 5.297 μs | 0.7160 μs | 0.0392 μs | 5.261 μs | 5.293 μs | 5.339 μs | 1.00 | 0.01 | 1 | 1.6785 | 0.0992 | 27.42 KB | 1.00 |
| AgileMapper | 5.228 μs | 1.2442 μs | 0.0682 μs | 5.170 μs | 5.211 μs | 5.303 μs | 0.98 | 0.01 | 1 | 1.0223 | 0.0610 | 16.72 KB | 0.61 |
🟠 Collection — 100-item List<T>
| Method | Mean | Error | StdDev | Min | Median | Max | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Manual | 17.34 μs | 5.023 μs | 0.275 μs | 17.18 μs | 17.19 μs | 17.66 μs | 1.00 | 0.02 | 1 | 5.2490 | 1.3123 | 85.99 KB | 1.00 |
| EggMapper | 17.50 μs | 2.291 μs | 0.126 μs | 17.36 μs | 17.57 μs | 17.58 μs | 1.01 | 0.02 | 1 | 5.2490 | 1.3123 | 85.99 KB | 1.00 |
| AutoMapper | 21.08 μs | 4.601 μs | 0.252 μs | 20.87 μs | 21.02 μs | 21.36 μs | 1.22 | 0.02 | 1 | 5.7678 | 1.4343 | 94.34 KB | 1.10 |
| Mapster | 17.57 μs | 1.884 μs | 0.103 μs | 17.45 μs | 17.60 μs | 17.65 μs | 1.01 | 0.01 | 1 | 5.2490 | 1.3123 | 85.99 KB | 1.00 |
| MapperlyMap | 19.45 μs | 8.310 μs | 0.456 μs | 19.12 μs | 19.27 μs | 19.97 μs | 1.12 | 0.03 | 1 | 5.2490 | 1.2817 | 86.02 KB | 1.00 |
| AgileMapper | 20.11 μs | 8.194 μs | 0.449 μs | 19.84 μs | 19.86 μs | 20.62 μs | 1.16 | 0.03 | 1 | 5.2795 | 1.3123 | 86.25 KB | 1.00 |
⚪ Startup / Configuration time
| Method | Mean | Error | StdDev | Min | Median | Max | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| EggMapperStartup | 1,339.970 μs | 1,233.4463 μs | 67.6094 μs | 1,283.450 μs | 1,321.591 μs | 1,414.869 μs | 1.002 | 0.06 | 3 | 3.9063 | 1.9531 | 94.48 KB | 1.00 |
| AutoMapperStartup | 422.794 μs | 1,110.5616 μs | 60.8736 μs | 361.480 μs | 423.685 μs | 483.217 μs | 0.316 | 0.04 | 2 | 5.8594 | - | 104.22 KB | 1.10 |
| MapsterStartup | 2.492 μs | 0.4879 μs | 0.0267 μs | 2.468 μs | 2.486 μs | 2.521 μs | 0.002 | 0.00 | 1 | 0.7019 | 0.0267 | 11.51 KB | 0.12 |
EggMapper.Benchmarks.ColdStartBenchmark-report-github
| Method | Mean | Error | StdDev | Min | Median | Max | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| EggMapper | 1.279 ms | 1.747 ms | 0.0958 ms | 1.185 ms | 1.274 ms | 1.376 ms | 1.00 | 0.09 | 1 | 5.8594 | - | 95.56 KB | 1.00 |
| AutoMapper | 4.072 ms | 7.553 ms | 0.4140 ms | 3.695 ms | 4.005 ms | 4.515 ms | 3.20 | 0.35 | 2 | 15.6250 | 7.8125 | 310.92 KB | 3.25 |
| Mapster | 4.479 ms | 8.312 ms | 0.4556 ms | 4.002 ms | 4.524 ms | 4.910 ms | 3.52 | 0.38 | 2 | 39.0625 | 15.6250 | 757.76 KB | 7.93 |
📝 Notes
- Each benchmark class is decorated with
[MemoryDiagnoser]and[RankColumn]. - The global config (see
src/EggMapper.Benchmarks/Program.cs) addsMin,Median, andMaxcolumns. - Manual is the hand-written baseline (ratio = 1.00). A ratio < 1 means faster than manual.
- Benchmarks run on GitHub-hosted runners — absolute times may vary between runs; focus on Ratio for comparisons.
- To reproduce locally:
cd src/EggMapper.Benchmarks dotnet run --configuration Release -- --filter '*'
This was referenced Mar 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When benchmark PRs merge to main, the push event can re-trigger benchmark and publish workflows — creating an infinite loop. The actor guard (
github.actor != 'github-actions[bot]') was unreliable because auto-merge usedPAT_TOKEN, making the merge actor the PAT owner instead of the bot.Fix — Three-layer defense
1. Path filters (primary, deterministic)
src/**,scripts/**,benchmarks.ymlchangessrc/**changes2. Actor guard (secondary safety net)
Both workflows keep
github.actor != 'github-actions[bot]'as fallback.3. Auto-merge uses GITHUB_TOKEN (not PAT_TOKEN)
github-actions[bot]instead of the PAT ownerPAT_TOKENstill used only for benchmark PR creation (required by org)Expected flow after this fix
Test plan
[skip ci])🤖 Generated with Claude Code