Skip to content

t4947: add cross-machine dispatch dedup via assignee check + pulse jitter#4948

Merged
marcusquinn merged 2 commits intomainfrom
bugfix/dispatch-race-assignee-check
Mar 15, 2026
Merged

t4947: add cross-machine dispatch dedup via assignee check + pulse jitter#4948
marcusquinn merged 2 commits intomainfrom
bugfix/dispatch-race-assignee-check

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Mar 15, 2026

Summary

  • Add dispatch-dedup-helper.sh is-assigned to check GitHub assignees before dispatch, so multi-machine runners share a single source of truth
  • Add startup jitter in pulse-wrapper.sh (PULSE_JITTER_MAX, default 30s) to desynchronize simultaneous pulse starts
  • Update pulse.md dedup sequence to include assignee guard with existing local process checks

Why

Local process dedup only sees workers on the current machine. When multiple runners evaluate the same issue at nearly the same time, both can dispatch before assignment propagates. GitHub assignee state + startup jitter prevent this race.

Verification

  • shellcheck .agents/scripts/dispatch-dedup-helper.sh .agents/scripts/pulse-wrapper.sh
  • dispatch-dedup-helper.sh is-assigned behavior verified for assigned-to-other (skip), assigned-to-self (allow), and unassigned (allow)

Closes #4947

Summary by CodeRabbit

  • New Features

    • Cross-machine deduplication added to avoid duplicate work when multiple automation runners handle the same issues; new CLI check lets the dispatcher respect existing assignees.
    • Startup jitter introduced to randomize instance startup timing and reduce simultaneous processing spikes.
  • Chores

    • Dispatcher now resolves runner identity earlier in the flow for more reliable dedup behavior.

…itter

Root cause: multiple pulse runners evaluating the same issue simultaneously
create duplicate PRs. Process-based dedup (has_worker_for_repo_issue,
is-duplicate) only sees local processes — invisible across machines.

Fix 1: Add is-assigned command to dispatch-dedup-helper.sh that queries
GitHub assignees before dispatch. If another runner already self-assigned,
skip the issue. This is the primary cross-machine dedup guard.

Fix 2: Add 0-30s random startup jitter to pulse-wrapper.sh so concurrent
launchd-triggered pulses don't evaluate issues at the same instant.
Configurable via PULSE_JITTER_MAX (set to 0 to disable).

Fix 3: Update pulse.md dispatch instructions to enforce the assignee
check as a mandatory step alongside existing local process dedup.

Observed: PR #4940 duplicated PR #4938 for issue #4937 because
alex-solovyev's pulse dispatched 2 min after marcusquinn self-assigned,
interpreting the in-progress worker as 'failed'.
@github-actions github-actions bot added the bug Auto-created from TODO.md tag label Mar 15, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical race condition that led to duplicate work dispatches across multiple runners. By implementing a GitHub-based assignee check and introducing a random startup delay, the system can now effectively prevent different machines from simultaneously processing the same issue, ensuring that work is assigned and executed uniquely. This significantly improves the robustness and efficiency of the dispatch mechanism.

Highlights

  • Cross-Machine Deduplication: Added an is-assigned command to dispatch-dedup-helper.sh which queries GitHub issue assignees to prevent duplicate dispatches across different runners. If an issue is already assigned to another runner, it is skipped.
  • Startup Jitter: Introduced a 0-30 second random startup jitter in pulse-wrapper.sh to stagger concurrent launchd-triggered pulses, reducing the chance of multiple runners evaluating the same issues simultaneously. This is configurable via PULSE_JITTER_MAX.
  • Documentation Update: Updated pulse.md dispatch instructions to enforce the new assignee check as a mandatory step, alongside existing local process deduplication checks.
Changelog
  • .agents/scripts/commands/pulse.md
    • Updated the 'Dedup guard' instruction to clarify that all three checks are required.
    • Introduced a new 'Cross-machine assignee dedup' step using dispatch-dedup-helper.sh is-assigned to check GitHub for existing assignees.
    • Reordered the RUNNER_USER assignment to occur before the dedup checks.
  • .agents/scripts/dispatch-dedup-helper.sh
    • Added a new shell function is_assigned to query GitHub for issue assignees.
    • The is_assigned function returns 0 if an issue is assigned to someone else (preventing dispatch) and 1 if unassigned or assigned to the current runner.
    • Updated the main function to handle the new is-assigned command.
    • Updated the help message to include the is-assigned command and its usage example.
  • .agents/scripts/pulse-wrapper.sh
    • Added a new section for 'Startup jitter' to desynchronize concurrent pulse instances.
    • Implemented a sleep command with a random duration (0-PULSE_JITTER_MAX, default 30s) at startup.
    • Made PULSE_JITTER_MAX configurable via an environment variable.
Activity
  • No human activity has occurred on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb5b912e-e0b4-4fa3-b580-0d1eec0513f7

📥 Commits

Reviewing files that changed from the base of the PR and between 39f4875 and 540ac93.

📒 Files selected for processing (2)
  • .agents/scripts/dispatch-dedup-helper.sh
  • .agents/scripts/pulse-wrapper.sh

Walkthrough

Adds a cross-machine dedup guard (is-assigned) that checks GitHub issue assignees before dispatch, moves RUNNER_USER resolution earlier in the dispatch flow, and introduces configurable startup jitter in pulse-wrapper to desynchronize concurrent pulse instances. (50 words)

Changes

Cohort / File(s) Summary
Dedup Guard Enhancement
​.agents/scripts/dispatch-dedup-helper.sh
Adds is_assigned() and is-assigned CLI command to query GitHub for issue assignees, returns skip/proceed semantics, updates help text and examples to include cross-machine assignee checks.
Dispatch Flow Update
​.agents/scripts/commands/pulse.md
Documents the dedup guard sequence now requiring three checks and shows RUNNER_USER being resolved earlier (via gh api user) before the dedup loop; updates examples and wording.
Startup Jitter
​.agents/scripts/pulse-wrapper.sh
Introduces PULSE_JITTER_MAX (default 30) and startup jitter logic to sleep a random 0..PULSE_JITTER_MAX seconds after PATH normalization to stagger concurrent pulse starts.

Sequence Diagram(s)

sequenceDiagram
    participant Runner as Runner Instance
    participant PulseWrapper as pulse-wrapper
    participant GitHub as GitHub API
    participant Dispatch as Dispatch Logic
    participant Helper as dispatch-dedup-helper

    Runner->>PulseWrapper: start pulse-wrapper.sh
    Note over PulseWrapper: apply startup jitter (random 0-30s)
    PulseWrapper->>GitHub: gh api user
    GitHub-->>PulseWrapper: RUNNER_USER (login)
    PulseWrapper->>Dispatch: provide RUNNER_USER

    Dispatch->>Dispatch: iterate open issues
    Dispatch->>Helper: is-assigned ISSUE_NUM SLUG "$RUNNER_USER"
    Helper->>GitHub: query issue assignees
    GitHub-->>Helper: assignees
    alt assigned to other runner
        Helper-->>Dispatch: exit 0 (skip)
        Dispatch->>Dispatch: skip issue
    else unassigned or self
        Helper-->>Dispatch: exit 1 (proceed)
        Dispatch->>GitHub: gh issue edit ... assign to RUNNER_USER
        Dispatch->>Dispatch: dispatch worker
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🌩️ Pulse wakes with jitter, stepping light,

Runners ask GitHub who holds the right,
If another claims it, silence keeps—
Else assign and dispatch where work sleeps,
A staggered rhythm guards the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning PR addresses assignee-based cross-machine dedup and jitter, but linked issue #4940 also requires wp-helper.sh SSH fixes which are not present in this changeset. Include changes to .agents/scripts/wp-helper.sh with build_remote_wp_cmd() function, updated SSH command construction, and print_info redirection to stderr as specified in #4940.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately describes the two main additions: a cross-machine dispatch dedup mechanism via assignee checking and startup jitter for pulse-wrapper.
Out of Scope Changes check ✅ Passed All three modified files (pulse.md, dispatch-dedup-helper.sh, pulse-wrapper.sh) are directly scoped to the dedup and jitter objectives; no unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/dispatch-race-assignee-check
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 362 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sun Mar 15 18:20:14 UTC 2026: Code review monitoring started
Sun Mar 15 18:20:15 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 362

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 362
  • VULNERABILITIES: 0

Generated on: Sun Mar 15 18:20:17 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@marcusquinn marcusquinn changed the title fix: prevent duplicate dispatch across runners via assignee check + jitter t4947: add cross-machine dispatch dedup via assignee check + pulse jitter Mar 15, 2026
@github-actions github-actions bot added the enhancement Auto-created from TODO.md tag label Mar 15, 2026
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a solid strategy to prevent duplicate work across runners by adding an assignee check and startup jitter. The changes are well-documented and address the race condition described. My feedback includes a couple of suggestions to enhance the robustness of the new shell scripts by improving input parsing and validation, ensuring they handle unexpected edge cases gracefully.

Comment on lines +77 to +84
PULSE_JITTER_MAX="${PULSE_JITTER_MAX:-30}"
if [[ "$PULSE_JITTER_MAX" -gt 0 ]]; then
# $RANDOM is 0-32767; modulo gives 0 to PULSE_JITTER_MAX
jitter_seconds=$((RANDOM % (PULSE_JITTER_MAX + 1)))
if [[ "$jitter_seconds" -gt 0 ]]; then
sleep "$jitter_seconds"
fi
fi

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The script assumes PULSE_JITTER_MAX is an integer. If a non-integer value is provided (e.g., from an environment variable), the script could fail on the arithmetic comparison or calculation, especially with set -e. It's safer to validate that the variable is numeric before using it in an arithmetic context.

Suggested change
PULSE_JITTER_MAX="${PULSE_JITTER_MAX:-30}"
if [[ "$PULSE_JITTER_MAX" -gt 0 ]]; then
# $RANDOM is 0-32767; modulo gives 0 to PULSE_JITTER_MAX
jitter_seconds=$((RANDOM % (PULSE_JITTER_MAX + 1)))
if [[ "$jitter_seconds" -gt 0 ]]; then
sleep "$jitter_seconds"
fi
fi
PULSE_JITTER_MAX="${PULSE_JITTER_MAX:-30}"
if [[ "$PULSE_JITTER_MAX" =~ ^[0-9]+$ && "$PULSE_JITTER_MAX" -gt 0 ]]; then
# $RANDOM is 0-32767; modulo gives 0 to PULSE_JITTER_MAX
jitter_seconds=$((RANDOM % (PULSE_JITTER_MAX + 1)))
if [[ "$jitter_seconds" -gt 0 ]]; then
sleep "$jitter_seconds"
fi
fi
References
  1. When parsing delimited data from an untrusted source in a shell script, validate numeric fields before using them in calculations. This provides defense-in-depth against injection attacks that could result from delimiter shifting caused by malicious data. This applies to environment variables as untrusted input.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 540ac93 — added numeric validation with [[ "$PULSE_JITTER_MAX" =~ ^[0-9]+$ ]] before the arithmetic comparison.

Comment on lines +327 to +334
local IFS=','
for assignee in $assignees; do
if [[ "$assignee" != "$self_login" ]]; then
dominated_by_self=false
break
fi
done
unset IFS

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While using local IFS=',' with a for ... in loop is a common idiom for iterating over comma-separated values, it can be brittle if values contain whitespace. A more robust method in bash is to use read -ra to parse the values into an array. This avoids unintended word splitting and handles various edge cases more gracefully, aligning with best practices for parsing delimited data in shell scripts.

Suggested change
local IFS=','
for assignee in $assignees; do
if [[ "$assignee" != "$self_login" ]]; then
dominated_by_self=false
break
fi
done
unset IFS
local IFS=','
read -ra assignee_array <<< "$assignees"
unset IFS
for assignee in "${assignee_array[@]}"; do
if [[ "$assignee" != "$self_login" ]]; then
dominated_by_self=false
break
fi
done
References
  1. In shell scripts, the read command's delimiter (set via IFS) should always match the delimiter of the input data to ensure correct parsing. This suggestion introduces read for more robust parsing.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 540ac93 — switched to IFS=',' read -ra assignee_array for robust parsing.

@marcusquinn
Copy link
Owner Author

Gemini Code Assist found 2 actionable findings. Dispatching worker to address before merge.

High: PULSE_JITTER_MAX integer validation — add [[ "$PULSE_JITTER_MAX" =~ ^[0-9]+$ ]] guard before arithmetic use.
Medium: Use read -ra for comma-split in is_assigned() — more robust than for ... in with IFS=','.

Both are genuine improvements, not noise.

…parsing

Address Gemini review feedback:
- Validate PULSE_JITTER_MAX is numeric before arithmetic (prevents
  set -e failures from non-integer env var values)
- Use read -ra for comma-separated assignee parsing instead of IFS
  word splitting (more robust against whitespace edge cases)
@github-actions
Copy link
Contributor

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 362 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sun Mar 15 18:24:02 UTC 2026: Code review monitoring started
Sun Mar 15 18:24:03 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 362

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 362
  • VULNERABILITIES: 0

Generated on: Sun Mar 15 18:24:05 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

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

Labels

bug Auto-created from TODO.md tag enhancement Auto-created from TODO.md tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: cross-machine dispatch dedup via GitHub assignee check + pulse startup jitter

1 participant