Skip to content

[3379 by Claude Opus 5.5] 0.9.0 release logic fix: multi-model runs on one issue silently drop every model whose job starts after the first model finishes, and leave those tasks stuck pending forever. Check current upstream main first. - #3384

Merged
integry merged 3 commits into
mainfrom
3379/claude-opus-5-5-0-9-0-release-logic-fix-m-20261029-1556-v73
Oct 29, 2026

Conversation

@propr-dev

@propr-dev propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #3379

Branch: 3379/claude-opus-5-5-0-9-0-release-logic-fix-m-20261029-1556-v73
Commits: ✅ Changes committed (fb8d98f)


AI Processing Completed

Execution Details:

Summary:
I've fixed #3379: when one request asks for several models, each model now runs even if another model already finished and labelled the issue AI-done. A skipped job's task no longer stays pending; it ends cancelled with a reason. Upstream main was the same commit as this branch (700ce82), so nothing there already covered it.

How sibling jobs are told apart

  • The matrix dispatcher now stamps every child job with a shared dispatchId, which is the dispatcher's BullMQ job id. A retried dispatch keeps that id, so children it re-adds are still siblings. Task replacements copy the field from their replay data.
  • In checkLabelConditions (src/jobs/issueJob/github.ts), an AI-done label no longer skips a job when all of these hold:
    • it has a dispatchId.
    • the issue was not already done when it was dispatched (from the label snapshot the dispatcher saves on the child).
    • its own task has not already completed.
  • The skip still applies to genuinely stale jobs:
    • jobs queued before this change (no dispatchId).
    • jobs dispatched after an earlier, separate request had finished the issue.
    • repeat deliveries of a model that already completed.

Skipped tasks now end

  • In src/jobs/processGitHubIssueJob.ts, a skipped job marks its task cancelled.
    • The AI-done skip uses a new reason, skipped_issue_done, shown as "Skipped because the issue was already completed by another run." It is not in the set of reasons that send a cancellation notification.
    • The primary-tag-removed skip uses the existing cancelled_label_removed.
  • A task that has already ended (for example a duplicate's completed one) keeps its outcome.
  • I checked the other early exits in the issue job: user-stopped, withdrawn, authentication failure and workflow-policy failure already end their tasks.

Labels: I took the issue's minimum option. The first model to finish still adds AI-done; it just no longer blocks the others. A later sibling re-adds AI-processing while it runs and swaps it back to AI-done when it finishes, so the issue ends AI-done only. I didn't delay AI-done until every sibling has ended, because that would mean changing every place that applies the label. Single-model runs behave as before.

Tests

  • New test/issueJobMultiModelSiblings.test.ts (4 tests):
    • with concurrency 1, three models all run and complete after the first one labels the issue done.
    • stale jobs (queued before this change, or dispatched after the issue was done) are skipped and their tasks end cancelled / skipped_issue_done.
    • a repeat delivery of a completed model is skipped and its task stays completed.
    • the primary-tag-removed skip ends its task with cancelled_label_removed. To reach this path I had to turn off the withdrawal check, which normally catches a removed label first.
  • Against the original code, 3 of the 4 fail. The repeat-delivery test passes either way; it guards against the new check letting too much through.
  • I updated test/worker.test.ts with the new return values and sibling cases, and test/issueJobDispatcher.handleDispatch.test.ts now checks dispatchId on the children.

Validation: all of these pass:

  • issue job, worker and task state suites: test/worker*.test.ts, taskSubmissionLifecycle, issueJobDispatcher*, taskIntentWithdrawal, repositoryWorkflowAdmission, workerStateManager, workerTaskStateRecovery, issueJobCompletion, plus the UI TaskStatusTable test.
  • root tsc --noEmit and eslint --max-warnings 0 src/.
  • npm run lint --workspaces --if-present -- --max-warnings 0.
  • the release:verify command you gave.

test/worker.test.ts needs PROPR_DEMO_MODE=true to run in this container. I didn't run the full test suite.

I added a 0.9.0 Fixed changelog bullet. Nothing was merged, tagged, published or deployed. No preview images, since the change isn't visible in the UI.

Three connectors (Gmail, Google Calendar and Google Drive) need authorizing in your claude.ai connector settings before they can be used. This task didn't need them.

Detailed Logs:

  • Conversation: 165 messages
  • Session: 03348913-c353-48f0-ba05-4e09aaf68c82

Log files stored at:

  • conversation: /usr/src/app/logs/executions/issue-3379-2026-10-29T16-09-50-261Z-conversation.json
  • output: /usr/src/app/logs/executions/issue-3379-2026-10-29T16-09-50-261Z-output.txt
Latest Conversation Messages
ASSISTANT: [content unavailable]

ASSISTANT: I've fixed #3379: when one request asks for several models, each model now runs even if another model already finished and labelled the issue `AI-done`. A skipped job's task no longer stays `pending`;...


This PR was created automatically by ProPR after processing issue #3379.


💡 Need changes?

Comment on this PR to request refinements — the AI agent monitors comments and will update the implementation based on your feedback. Keep iterating until you're satisfied!

…l now runs even if another model already finished and labelled the issue `AI-done`. A skipped job's task no longer stays `pending`; it ends `cancelled` with a reason. Upstream `main` was the same commit as this branch (700ce82), so nothing there already covered it.

How sibling jobs are told apart
- The matrix dispatcher now stamps every child job with a shared `dispatchId`, which is the dispatcher's BullMQ job id. A retried dispatch keeps that id, so children it re-adds are still siblings. Task replacements copy the field from their replay data.
- In `checkLabelConditions` (`src/jobs/issueJob/github.ts`), an `AI-done` label no longer skips a job when all of these hold:
  - it has a `dispatchId`.
  - the issue was not already done when it was dispatched (from the label snapshot the dispatcher saves on the child).
  - its own task has not already completed.
- The skip still applies to genuinely stale jobs:
  - jobs queued before this change (no `dispatchId`).
  - jobs dispatched after an earlier, separate request had finished the issue.
  - repeat deliveries of a model that already completed.

Skipped tasks now end
- In `src/jobs/processGitHubIssueJob.ts`, a skipped job marks its task `cancelled`.
  - The `AI-done` skip uses a new reason, `skipped_issue_done`, shown as "Skipped because the issue was already completed by another run." It is not in the set of reasons that send a cancellation notification.
  - The primary-tag-removed skip uses the existing `cancelled_label_removed`.
- A task that has already ended (for example a duplicate's completed one) keeps its outcome.
- I checked the other early exits in the issue job: user-stopped, withdrawn, authentication failure and workflow-policy failure already end their tasks.

Labels: I took the issue's minimum option. The first model to finish still adds `AI-done`; it just no longer blocks the others. A later sibling re-adds `AI-processing` while it runs and swaps it back to `AI-done` when it finishes, so the issue ends `AI-done` only. I didn't delay `AI-done` until every sibling has ended, because that would mean changing every place that applies the label. Single-model runs behave as before.

Tests
- New `test/issueJobMultiModelSiblings.test.ts` (4 tests):
  - with concurrency 1, three models all run and complete after the first one labels the issue done.
  - stale jobs (queued before this change, or dispatched after the issue was done) are skipped and their tasks end `cancelled` / `skipped_issue_done`.
  - a repeat delivery of a completed model is skipped and its task stays `completed`.
  - the primary-tag-removed skip ends its task with `cancelled_label_removed`. To reach this path I had to turn off the withdrawal check, which normally catches a removed label first.
- Against the original code, 3 of the 4 fail. The repeat-delivery test passes either way; it guards against the new check letting too much through.
- I updated `test/worker.test.ts` with the new return values and sibling cases, and `test/issueJobDispatcher.handleDispatch.test.ts` now checks `dispatchId` on the children.

Validation: all of these pass:
- issue job, worker and task state suites: `test/worker*.test.ts`, `taskSubmissionLifecycle`, `issueJobDispatcher*`, `taskIntentWithdrawal`, `repositoryWorkflowAdmission`, `workerStateManager`, `workerTaskStateRecovery`, `issueJobCompletion`, plus the UI `TaskStatusTable` test.
- root `tsc --noEmit` and `eslint --max-warnings 0 src/`.
- `npm run lint --workspaces --if-present -- --max-warnings 0`.
- the `release:verify` command you gave.

`test/worker.test.ts` needs `PROPR_DEMO_MODE=true` to run in this container. I didn't run the full test suite.

I added a 0.9.0 Fixed changelog bullet. Nothing was merged, tagged, published or deployed. No preview images, since the change isn't visible in the UI.

Three connectors (Gmail, Google Calendar and Google Drive) need authorizing in your claude.ai connector settings before they can be used. This task didn't need them.
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

The PR fixes the normal sequential multi-model case, but needs a localized correction before merge: a partially successful dispatcher retry can still drop remaining models.

✅ Sibling continuation — Children dispatched before AI-done can proceed after an earlier model finishes, with regression coverage at concurrency 1.

✅ Explicit skip outcomes — Normal skipped jobs receive a cancellation reason, while already-terminal task outcomes are preserved.

This review uses static analysis only; no commands were run. The supplied issue body is truncated, so requirements beyond its visible text were not assessed. Current checks report no failures and seven pending checks.

Merge blockers

Every finding below was introduced by this PR and must be resolved before merging.

F1: 🔴 Preserve eligibility across dispatcher retries

  • Required behavior: Every model in the same request must remain eligible after another model finishes, including children enqueued by a retry of a partially successful dispatch.

  • Evidence: src/jobs/issueJobDispatcher.ts:handleDispatchWithDeps, particularly the changed dispatchId assignment and adjacent issuePayload assignment; src/jobs/issueJob/github.ts:isUnfinishedDispatchSibling.

    1. A dispatch selects multiple models while the issue has AI and no AI-done. For an ordinary submission without a retry request, enqueueChildren adds children individually.
    2. Adding the first child succeeds, but a subsequent issueQueue.add fails. The dispatcher throws before recording its dispatch receipt.
    3. The accepted child completes and adds AI-done before the dispatcher resumes. This ordering is possible with multiple worker slots, or while the dispatcher awaits retry after an interruption; the individual adds are not transactional.
    4. The retried dispatcher fetches the issue again. It retains the same dispatcher job ID, but constructs remaining children with the freshly fetched issuePayload, which now contains AI-done.
    5. Those children reach isUnfinishedDispatchSibling, which returns false because their snapshot contains the done label. They are skipped and cancelled instead of executing the remaining requested models.

    The stable dispatchId does not protect this sequence: the eligibility check only tests its presence and requires the per-attempt snapshot to lack AI-done. Deterministic child IDs prevent duplicate retained children, but do not preserve eligibility for children whose original enqueue failed. The added tests construct all children with an unfinished snapshot and therefore do not exercise this recovery path.

    static trace: Verified against the supplied dispatcher enqueue loop, receipt ordering, fresh issue fetch, and changed sibling predicate. No regression test was executed.

  • Minimum fix: Persist the dispatch’s original unfinished eligibility before enqueueing any children and reuse it when that dispatch retries. Preserve the rejection of genuinely new dispatches that first observe an already-done issue. Add a regression covering partial enqueue failure, first-child completion, and successful execution of the remaining children after retry.

Suggestions

These are optional follow-ups and are not sent to /fix.

No suggestions.

Score

The normal execution path and terminal-reason handling are well covered, but dispatcher recovery still reproduces the central dropped-model failure. Pending checks have not reported failures.

Score: 6/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 1m 18s
  • Tokens: 48,634 (47,537 in / 1,097 out)
  • Cost: $0.42

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix F1

🤖 Review by ProPR

@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/fix F1
Keep it minimal: record the dispatch's original 'issue unfinished' eligibility once (for example in the dispatch receipt/job data before the first child is enqueued) and reuse it on dispatcher retries. Add the partial-enqueue-failure regression the review describes.

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Applied the requested follow-up changes in commit 894966a

Addressed 1 AI review comment (IDs: 6502092023)

Addressed finding F1

Summary of Changes

I've fixed F1. A dispatch now saves the issue's labels once, before it enqueues its first child, and every retry of that dispatch reuses the saved labels instead of the issue it fetches again. In your partial-failure scenario, the remaining models now run after the retry.

What changed

  • Dispatcher (src/jobs/issueJobDispatcher.ts, firstDispatchLabels): on the first attempt, the issue's label names are written to the dispatcher job as dispatchIssueLabels via job.updateData, before any child is added. A retry finds them in its stored job data and uses them. Each child copies them.
  • The check itself (src/jobs/issueJob/github.ts:isUnfinishedDispatchSibling): it now reads dispatchIssueLabels instead of the per-attempt issuePayload.
  • New dispatches are still rejected as before. If a dispatch first sees the issue already labelled AI-done, that label is saved with it, so its children are still skipped.
  • The new field is declared on IssueJobData in packages/core/src/queue/taskQueue.types.ts.

Same problem in replacement tasks. When a task is replaced, its replay data drops issuePayload (TRANSIENT_JOB_FIELDS in src/taskReplacement/store.ts). So a replacement for a sibling had a dispatchId but no labels to check, and was skipped once the issue was done. dispatchIssueLabels is not dropped on replay, so the same change fixes this. I added an assertion for it in test/worker.test.ts.

Tests

  • New regression in test/issueJobMultiModelSiblings.test.ts, run through the real dispatcher and issue-job path:
    1. The second issueQueue.add fails, so the dispatcher throws with only model-a queued.
    2. model-a runs and labels the issue AI-done.
    3. The dispatcher retries from its stored job data and re-adds model-b and model-c.
    4. Both run, and all three tasks end completed.
    5. A fresh dispatch on the now-done issue still has its child skipped as "Already done".
  • It fails on the old code, at "model-b ran after the retried dispatch", which is the scenario from the review.
  • Fixture updates: the existing sibling fixtures now use dispatchIssueLabels. The existing dispatcher test's job got an updateData stub, because the dispatcher now writes job data.
  • Results:
    • The sibling, dispatcher and worker suites pass (12/12).
    • The submission-lifecycle, dispatcher, replacement and retry suites pass (166/166).
    • tsc --noEmit is clean.
    • ESLint reports no problems in the changed src/ files. The only warnings say the test files fall outside the lint config.

There's no visual change, so I didn't make previews.


🤖 Implementation Details

  • Model: Claude Opus 5.5 (medium)
  • Requested By: @integry
  • Time: 7m 15s
  • Tokens: 1,774,134 (1,759,840 in / 14,294 out)
  • Cost: $0.95
  • Subscription usage: Session +3%, Fable +1%

Undo Changes • View Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Address review blockers, or blockers and suggestions with /fix all /fix, /fix all, or /fix F3 S5
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment ID: 6504368318✓

…, before it enqueues its first child, and every retry of that dispatch reuses the saved labels instead of the issue it fetches again. In your partial-failure scenario, the remaining models now run after the retry.

I've fixed F1. A dispatch now saves the issue's labels once, before it enqueues its first child, and every retry of that dispatch reuses the saved labels instead of the issue it fetches again. In your partial-failure scenario, the remaining models now run after the retry.

**What changed**
- **Dispatcher** (`src/jobs/issueJobDispatcher.ts`, `firstDispatchLabels`): on the first attempt, the issue's label names are written to the dispatcher job as `dispatchIssueLabels` via `job.updateData`, before any child is added. A retry finds them in its stored job data and uses them. Each child copies them.
- **The check itself** (`src/jobs/issueJob/github.ts:isUnfinishedDispatchSibling`): it now reads `dispatchIssueLabels` instead of the per-attempt `issuePayload`.
- **New dispatches are still rejected as before.** If a dispatch first sees the issue already labelled `AI-done`, that label is saved with it, so its children are still skipped.
- The new field is declared on `IssueJobData` in `packages/core/src/queue/taskQueue.types.ts`.

**Same problem in replacement tasks.** When a task is replaced, its replay data drops `issuePayload` (`TRANSIENT_JOB_FIELDS` in `src/taskReplacement/store.ts`). So a replacement for a sibling had a `dispatchId` but no labels to check, and was skipped once the issue was done. `dispatchIssueLabels` is not dropped on replay, so the same change fixes this. I added an assertion for it in `test/worker.test.ts`.

**Tests**
- **New regression** in `test/issueJobMultiModelSiblings.test.ts`, run through the real dispatcher and issue-job path:
  1. The second `issueQueue.add` fails, so the dispatcher throws with only `model-a` queued.
  2. `model-a` runs and labels the issue `AI-done`.
  3. The dispatcher retries from its stored job data and re-adds `model-b` and `model-c`.
  4. Both run, and all three tasks end `completed`.
  5. A fresh dispatch on the now-done issue still has its child skipped as "Already done".
- **It fails on the old code**, at "model-b ran after the retried dispatch", which is the scenario from the review.
- **Fixture updates:** the existing sibling fixtures now use `dispatchIssueLabels`. The existing dispatcher test's job got an `updateData` stub, because the dispatcher now writes job data.
- **Results:**
  - The sibling, dispatcher and worker suites pass (12/12).
  - The submission-lifecycle, dispatcher, replacement and retry suites pass (166/166).
  - `tsc --noEmit` is clean.
  - ESLint reports no problems in the changed `src/` files. The only warnings say the test files fall outside the lint config.

There's no visual change, so I didn't make previews.

PR: #3384
Comment by: @integry (ID: 6504368318)
Model: Claude Opus 5.5
@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

CI failed: Full Test Suite Shard 3/4

Please investigate and fix this CI failure.

  • Check: Full Test Suite Shard 3/4
  • Result: failure
  • Commit: 894966a90ae7 (894966a90ae764f28ef239cfc616baed3174b854)
  • Details: View CI failure

Failure evidence

.github:11
Process completed with exit code 1.

.github:39571
Process completed with exit code 1.

.github:2
Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@11d5960. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

CI failed: Full Test Suite Shard 4/4

Please investigate and fix this CI failure.

  • Check: Full Test Suite Shard 4/4
  • Result: failure
  • Commit: 894966a90ae7 (894966a90ae764f28ef239cfc616baed3174b854)
  • Details: View CI failure

Failure evidence

.github:11
Process completed with exit code 1.

.github:36376
Process completed with exit code 1.

.github:2
Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@11d5960. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

CI failed: Full Test Suite Shard 1/4

Please investigate and fix this CI failure.

  • Check: Full Test Suite Shard 1/4
  • Result: failure
  • Commit: 894966a90ae7 (894966a90ae764f28ef239cfc616baed3174b854)
  • Details: View CI failure

Failure evidence

.github:11
Process completed with exit code 1.

.github:30050
Process completed with exit code 1.

.github:2
Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@11d5960. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/

@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

The PR addresses delayed sibling execution by preserving dispatch eligibility and records terminal outcomes for skipped tasks. No merge-blocking code defect was verified from the supplied code, but the current failed checks prevent calling it merge-ready.

✅ Retry-stable eligibility — The dispatcher persists its original labels before enqueueing children and reuses them on retries.

✅ Targeted regression coverage — The added test covers partial enqueue failure, completion of the first child, and successful execution of remaining models after dispatcher retry.

✅ Preserved task outcomes — Skip handling preserves existing terminal states and supplies explicit cancellation reasons.

This was a static review; no commands or tests were run. The supplied issue body is truncated, so objective validation is limited to its visible requirements.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S1: 🟢 Investigate current test failures

The authoritative status reports failures in test shards 1, 3, and 4. Inspect their diagnostics to establish whether they involve this change. The supplied evidence does not connect those failures to changed code, so they are not code findings; further investigation is needed to establish merge readiness.

Score

The implementation and targeted regression coverage support the visible objective. Current test failures leave validation unresolved despite no verified code blocker.

Score: 7/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 57s
  • Tokens: 48,802 (48,157 in / 645 out)
  • Cost: $0.40

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S1

🤖 Review by ProPR

@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

The full test suite fails on 894966a. All 7 failures are in the task follow-up-issue family, so this looks like a regression from the dispatcher or label-check change, not a flake:

  • shard 1: packages/api/test/taskFollowupIssuePublication.test.ts, taskFollowupIssueRestart.test.ts, taskFollowupIssueWithdrawal.test.ts
  • shard 3: taskFollowupIssueLabelChange.test.ts, taskFollowupIssueRetrySettlement.test.ts
  • shard 4: taskFollowupIssueOutcomeRecovery.test.ts, taskFollowupIssueRoutes.test.ts

Shard logs show repeated [followup] Adding 'AI' to #14 failed (no response): Request timed out and Connection timed out, which suggests the follow-up issue path now waits on or calls something it didn't before (for example the new dispatch snapshot or label lookup). Reproduce locally by running those 7 files, then fix the root cause in the PR's change without weakening the tests. Keep the multi-model sibling behaviour, the F1 retry eligibility and the terminal states for skipped jobs. Run all packages/api/test/taskFollowupIssue*.test.ts plus the issue job/dispatcher tests before pushing.

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Applied the requested follow-up changes in commit 1174b4b

Summary of Changes

I've fixed the 7 follow-up issue test failures. It was a regression from the F1 change, not a flake, and all 7 files now pass locally. No test assertions were changed.

Cause: the F1 change saves the issue's labels by calling job.updateData inside handleDispatchWithDeps. Before that, the dispatcher never wrote to the job directly; every outside call came in through its injected dependencies. The follow-up issue tests pass the dispatcher a plain { id, name, data } job with no updateData method. So each dispatch failed with TypeError: job.updateData is not a function before it could add any child job.

The timeout messages are not the cause. Adding 'AI' to #14 failed: Request timed out and Connection timed out appear 4 times both before and after the fix. They come from scenarios the tests simulate deliberately.

Fix (src/jobs/issueJobDispatcher.ts):

  • The dispatcher's dependencies now include an optional saveDispatchData. The real dispatcher sets it to job.updateData, so production still saves the labels once, before the first child is added.
  • Callers that don't supply it (the follow-up and core submission tests) get the dispatcher's behaviour from before F1, for that harness only.
  • The F1 retry regression test in test/issueJobMultiModelSiblings.test.ts now passes saveDispatchData explicitly.

The multi-model sibling behaviour, the F1 retry eligibility and the cancelled / skipped_issue_done end state for skipped jobs are unchanged.

Checks:

  • All 30 related test files pass: every packages/api/test/taskFollowupIssue*.test.ts, the root issueJob* and dispatcher tests, processGitHubIssueJob*, worker.test.ts, and the core taskSubmissionService and taskSubmissionIntake tests.
  • With saveDispatchData removed, the F1 regression test fails at "model-b ran after the retried dispatch", so it still catches the original bug.
  • Root tsc --noEmit passes, and ESLint with --max-warnings 0 is clean on the dispatcher.

There are no visual changes, so I didn't create any previews.


🤖 Implementation Details

  • Model: Claude Opus 5.5 (medium)
  • Requested By: @integry
  • Time: 6m 52s
  • Tokens: 891,106 (883,860 in / 7,246 out)
  • Cost: $0.59
  • Subscription usage: Session +4%, Weekly +1%

Undo Changes • View Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Address review blockers, or blockers and suggestions with /fix all /fix, /fix all, or /fix F3 S5
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment ID: 6505723310✓

…gression from the F1 change, not a flake, and all 7 files now pass locally. No test assertions were changed.

I've fixed the 7 follow-up issue test failures. It was a regression from the F1 change, not a flake, and all 7 files now pass locally. No test assertions were changed.

**Cause:** the F1 change saves the issue's labels by calling `job.updateData` inside `handleDispatchWithDeps`. Before that, the dispatcher never wrote to the job directly; every outside call came in through its injected dependencies. The follow-up issue tests pass the dispatcher a plain `{ id, name, data }` job with no `updateData` method. So each dispatch failed with `TypeError: job.updateData is not a function` before it could add any child job.

**The timeout messages are not the cause.** `Adding 'AI' to #14 failed: Request timed out` and `Connection timed out` appear 4 times both before and after the fix. They come from scenarios the tests simulate deliberately.

**Fix** (`src/jobs/issueJobDispatcher.ts`):
- The dispatcher's dependencies now include an optional `saveDispatchData`. The real dispatcher sets it to `job.updateData`, so production still saves the labels once, before the first child is added.
- Callers that don't supply it (the follow-up and core submission tests) get the dispatcher's behaviour from before F1, for that harness only.
- The F1 retry regression test in `test/issueJobMultiModelSiblings.test.ts` now passes `saveDispatchData` explicitly.

The multi-model sibling behaviour, the F1 retry eligibility and the `cancelled` / `skipped_issue_done` end state for skipped jobs are unchanged.

**Checks:**
- All 30 related test files pass: every `packages/api/test/taskFollowupIssue*.test.ts`, the root `issueJob*` and dispatcher tests, `processGitHubIssueJob*`, `worker.test.ts`, and the core `taskSubmissionService` and `taskSubmissionIntake` tests.
- With `saveDispatchData` removed, the F1 regression test fails at "model-b ran after the retried dispatch", so it still catches the original bug.
- Root `tsc --noEmit` passes, and ESLint with `--max-warnings 0` is clean on the dispatcher.

There are no visual changes, so I didn't create any previews.

PR: #3384
Comment by: @integry (ID: 6505723310)
Model: Claude Opus 5.5
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

The PR is merge-ready within the visible scope, conditional on the pending Validate Changes check completing successfully.

✅ Retry-stable eligibility — The dispatcher saves its initial labels before enqueueing children and reuses them on retries. The partial-enqueue regression covers a sibling finishing before dispatch resumes.

✅ Explicit skipped-task outcomes — Skipped jobs receive cancellation reasons, while existing terminal outcomes are preserved. Replacement payloads retain dispatch eligibility.

✅ Focused regression coverage — Tests cover sequential model execution, stale dispatches, duplicate deliveries, and removed trigger labels. The supplied current-head status reports all four full-suite shards passing.

This was a static review; no commands were run. The original issue description is explicitly truncated, so assessment is limited to its visible requirements and the supplied authorized follow-up.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S2: 🟢 Cover custom trigger labels

Add a sibling-execution case using a configured trigger other than AI, including its corresponding done label. The implementation correctly accepts the configured label, but current regression cases exercise only AI. This is optional coverage for an existing supported configuration.

Score

The implementation addresses the visible objective with focused changes and relevant regression tests. Current checks show no failures, with one validation check still pending.

Score: 9/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 47s
  • Tokens: 50,181 (49,489 in / 692 out)
  • Cost: $0.42

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S2

🤖 Review by ProPR

@integry
integry merged commit 41a7706 into main Oct 29, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant