Skip to content

fix: prevent benchmark/publish infinite loop with path filters#79

Merged
github-actions[bot] merged 1 commit intomainfrom
fix/workflow-path-filters
Mar 26, 2026
Merged

fix: prevent benchmark/publish infinite loop with path filters#79
github-actions[bot] merged 1 commit intomainfrom
fix/workflow-path-filters

Conversation

@cloud-hai-vo
Copy link
Copy Markdown
Contributor

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 used PAT_TOKEN, making the merge actor the PAT owner instead of the bot.

Fix — Three-layer defense

1. Path filters (primary, deterministic)

  • Benchmark workflow: only triggers on src/**, scripts/**, benchmarks.yml changes
  • 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]' as fallback.

3. Auto-merge uses GITHUB_TOKEN (not PAT_TOKEN)

  • Merge actor is now github-actions[bot] instead of the PAT owner
  • Actor guards work correctly as secondary defense
  • PAT_TOKEN still used only for benchmark PR creation (required by org)

Expected flow after this fix

Code PR merges → benchmarks run (src/** changed) → creates README PR
  → CI runs → auto-merge squashes to main
  → push event fires but paths: only README.md → benchmark SKIPS ✓
  → publish SKIPS ✓

Test plan

  • Merge this PR → should trigger benchmarks (workflow YAML changed is in paths)
  • Benchmark PR created → CI runs on it (no [skip ci])
  • Benchmark PR merges → no re-trigger of benchmark or publish

🤖 Generated with Claude Code

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>
@github-actions github-actions Bot enabled auto-merge (squash) March 26, 2026 04:44
@github-actions github-actions Bot merged commit 3a506f9 into main Mar 26, 2026
5 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

📊 Benchmark Results

Generated: 2026-03-26 04:51 UTC  ·  Download full artifacts

Column guide:
Mean = average execution time  ·  Error = half of 99.9 % confidence interval  ·  StdDev = standard deviation  ·  Min / Median / Max = statistical range  ·  Ratio = vs Manual baseline (lower = closer to hand-written speed)  ·  RatioSD = ratio std dev  ·  Rank = 1 is fastest  ·  Gen0/1/2 = GC collections per 1 000 ops  ·  Allocated = managed heap per operation  ·  Alloc Ratio = allocation ratio vs baseline

🔵 Flat Mapping — 10-property object

Method Mean Error StdDev Min Median Max Ratio RatioSD Rank Gen0 Allocated Alloc Ratio
Manual 15.04 ns 1.258 ns 0.069 ns 14.97 ns 15.04 ns 15.11 ns 1.00 0.01 1 0.0048 80 B 1.00
EggMapper 26.40 ns 8.424 ns 0.462 ns 26.00 ns 26.30 ns 26.91 ns 1.76 0.03 2 0.0048 80 B 1.00
AutoMapper 82.03 ns 1.778 ns 0.097 ns 81.94 ns 82.01 ns 82.13 ns 5.45 0.02 3 0.0048 80 B 1.00
Mapster 27.80 ns 0.985 ns 0.054 ns 27.74 ns 27.83 ns 27.83 ns 1.85 0.01 2 0.0048 80 B 1.00
MapperlyMap 15.53 ns 6.770 ns 0.371 ns 15.15 ns 15.56 ns 15.89 ns 1.03 0.02 1 0.0048 80 B 1.00
AgileMapper 493.30 ns 116.242 ns 6.372 ns 486.47 ns 494.37 ns 499.08 ns 32.80 0.39 4 0.0200 344 B 4.30
EggMapperGenerator 15.70 ns 9.889 ns 0.542 ns 15.23 ns 15.57 ns 16.30 ns 1.04 0.03 1 0.0048 80 B 1.00
EggMapperClassMapper 15.20 ns 6.255 ns 0.343 ns 14.84 ns 15.24 ns 15.52 ns 1.01 0.02 1 0.0048 80 B 1.00

🟡 Flattening — 2 nested objects → 8 flat properties

Method Mean Error StdDev Min Median Max Ratio RatioSD Rank Gen0 Allocated Alloc Ratio
Manual 18.90 ns 1.561 ns 0.086 ns 18.81 ns 18.91 ns 18.98 ns 1.00 0.01 1 0.0048 80 B 1.00
EggMap 30.39 ns 9.201 ns 0.504 ns 30.04 ns 30.16 ns 30.97 ns 1.61 0.02 3 0.0048 80 B 1.00
AutoMapper 92.92 ns 5.924 ns 0.325 ns 92.71 ns 92.76 ns 93.30 ns 4.92 0.02 4 0.0048 80 B 1.00
Mapster 36.35 ns 12.615 ns 0.691 ns 35.90 ns 36.00 ns 37.15 ns 1.92 0.03 3 0.0048 80 B 1.00
MapperlyMap 23.36 ns 2.655 ns 0.146 ns 23.27 ns 23.29 ns 23.53 ns 1.24 0.01 2 0.0062 104 B 1.30
AgileMapper 496.86 ns 57.058 ns 3.128 ns 493.51 ns 497.39 ns 499.70 ns 26.29 0.18 5 0.0200 344 B 4.30

🟣 Deep Mapping — 2 nested address objects

Method Mean Error StdDev Min Median Max Ratio RatioSD Rank Gen0 Allocated Alloc Ratio
Manual 53.52 ns 2.540 ns 0.139 ns 53.43 ns 53.46 ns 53.68 ns 1.00 0.00 1 0.0162 272 B 1.00
EggMapper 65.12 ns 24.329 ns 1.334 ns 63.81 ns 65.08 ns 66.48 ns 1.22 0.02 2 0.0162 272 B 1.00
AutoMapper 128.54 ns 45.874 ns 2.514 ns 126.18 ns 128.27 ns 131.18 ns 2.40 0.04 3 0.0162 272 B 1.00
Mapster 67.25 ns 22.293 ns 1.222 ns 65.84 ns 67.91 ns 68.00 ns 1.26 0.02 2 0.0162 272 B 1.00
MapperlyMap 53.15 ns 56.150 ns 3.078 ns 49.63 ns 54.48 ns 55.34 ns 0.99 0.05 1 0.0162 272 B 1.00
AgileMapper 519.46 ns 32.963 ns 1.807 ns 517.39 ns 520.28 ns 520.71 ns 9.71 0.04 4 0.0248 424 B 1.56

🟢 Complex Mapping — nested object + collection

Method Mean Error StdDev Min Median Max Ratio RatioSD Rank Gen0 Allocated Alloc Ratio
Manual 70.79 ns 10.940 ns 0.600 ns 70.17 ns 70.84 ns 71.37 ns 1.00 0.01 1 0.0191 320 B 1.00
EggMapper 90.87 ns 25.561 ns 1.401 ns 89.41 ns 90.99 ns 92.21 ns 1.28 0.02 2 0.0191 320 B 1.00
AutoMapper 158.32 ns 21.687 ns 1.189 ns 156.96 ns 158.88 ns 159.13 ns 2.24 0.02 3 0.0196 328 B 1.02
Mapster 88.89 ns 9.991 ns 0.548 ns 88.28 ns 89.05 ns 89.34 ns 1.26 0.01 2 0.0191 320 B 1.00
MapperlyMap 72.00 ns 10.467 ns 0.574 ns 71.35 ns 72.23 ns 72.42 ns 1.02 0.01 1 0.0191 320 B 1.00
AgileMapper 577.60 ns 52.804 ns 2.894 ns 574.26 ns 579.17 ns 579.36 ns 8.16 0.07 4 0.0315 528 B 1.65

🟠 Collection — 100-item List<T>

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) adds Min, Median, and Max columns.
  • 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 '*'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant