Skip to content

fix(scheduling): settle expandable-node request correlation on non-dispatch paths - #189

Merged
Bnjoroge1 merged 3 commits into
mainfrom
Bnjoroge/deferred-matrix-correlation
Aug 28, 2026
Merged

Bnjoroge1 merged 3 commits into
mainfrom
Bnjoroge/deferred-matrix-correlation

Conversation

@Bnjoroge1

@Bnjoroge1 Bnjoroge1 commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

MC-3: submit mints a full request-correlation record set for every non-caller job, including needs-deferred dynamic-matrix nodes. Those nodes route to expansion and never dispatch to a runner, so no completion, result patch, or disconnect ever settles their records. On every path where such a node concludes without dispatching, the record leaked in inflight_requests and session_active_requests with result: None — staying renewable and resolvable to a job that expansion had already deleted from the run.

Fix

Settle the record (result recorded, out of inflight + every session) exactly as the completion path would, on all non-dispatch conclusions:

  • run cancel (cancel_run_inner): use a run-wide expandable_job_ids set instead of the narrow caller_plans-only collection.
  • job cancel (cancel_job_inner): settle via is_expandable_node.
  • submit-time workflow-concurrency overflow cancel (submit_run_inner).
  • dependency skip / if: eval error (promote_ready_jobs Skip/Error arm).

Reusable callers mint no records at submit, so retiring them is a no-op; Settle is idempotent, so overlapping paths cannot double-settle harmfully. id_token_grants / oidc_job_contexts are deliberately kept (mirrors completed/cancelled real jobs).

Tests

Adds coverage for all four non-dispatch conclusions:

  • cancelled_deferred_matrix_node_settles_submit_requests
  • cancelled_deferred_matrix_node_job_cancel_settles_requests
  • overflowed_run_settles_deferred_matrix_node_requests
  • dependency_skipped_deferred_matrix_node_settles_requests
  • dependency_error_deferred_matrix_node_settles_requests

Verification

Rebased onto current main. cargo build, all 5 MC-3 tests, cargo fmt --check, and cargo clippy clean on preloop-runner-server.


Summary by cubic

Fixes MC-3: request-correlation records for needs-deferred dynamic-matrix nodes kept leaking in inflight_requests and session_active_requests when the node concluded without ever dispatching. These submit-time records are now settled exactly like a completed job — result recorded, removed from inflight and every session — on all non-dispatch conclusion paths.

Paths settled

  • Run cancel gathers a run-wide expandable_job_ids set from all queues, replacing the narrow caller_plans-only collection.
  • Job cancel settles expandable nodes via is_expandable_node.
  • Workflow-concurrency overflow cancels at submit time now settle expandable nodes.
  • The promote_ready_jobs Skip/Error arm settles expandable nodes with Skipped or Failure.

Settlement uses each node's own concluded status from the run, so an already-terminal expandable node (like a reusable caller that finished Success while the run stayed active) keeps its verdict instead of being overwritten to Cancelled. Settle is idempotent, and reusable callers mint no records at submit, so overlapping paths and caller retirement are no-ops; id_token_grants and oidc_job_contexts are deliberately kept to mirror completed jobs. Adds six tests covering each non-dispatch conclusion plus completed-caller preservation.

Written for commit 2842849. Summary will update on new commits.

Review in cubic

Note

Settle expandable-node request correlations on run/job cancel and dependency skip paths

  • Expandable nodes (deferred-matrix placeholders and reusable callers) had their submit-time request records left inflight when a run or job was cancelled or when a dependency decision skipped or errored the node. This PR settles those records with the node's actual concluded status and clears inflight/session claims.
  • cancel_run_inner and cancel_job_inner now use expandable_job_ids / is_expandable_node to detect all expandable nodes and call retire_node_requests with node_settle_status, preserving terminal verdicts like Success for already-completed reusable callers instead of forcing Cancelled.
  • promote_ready_jobs settles request records for expandable nodes on dependency Skip/Error outcomes; submit_run_inner settles expandable nodes when a run is immediately cancelled on acceptance.
  • RequestRetirement::Settle retains id_token_grants and oidc_job_contexts for settled nodes while clearing inflight and session-active claims.
  • Behavioral Change: cancellation and dependency-skip paths now settle expandable-node request records with their actual job status (via node_settle_status in runtime_scheduling.rs) rather than leaving them inflight or hardcoding Cancelled; verify consumers of plan_requests, agent_job_requests, and timeline_requests still resolve to settled placeholders.

Macroscope summarized 2842849.

Summary by CodeRabbit

  • Bug Fixes

    • Improved cancellation handling for deferred matrix and reusable workflow jobs, ensuring requests are settled cleanly.
    • Preserved successful results from completed nested workflow calls when remaining work is cancelled.
    • Fixed workflow concurrency overflow cleanup so deferred jobs are marked cancelled promptly.
    • Prevented cancelled operations from overwriting completed successful outcomes.
  • Tests

    • Added regression coverage for nested workflow cancellation and successful result preservation.

…spatch paths

MC-3: submit mints a full request-correlation record set for every
non-caller job, including needs-deferred dynamic-matrix nodes. Those nodes
route to expansion and never dispatch to a runner, so no completion, result
patch or disconnect ever settles their records. On every path where such a
node concludes without dispatching, the record leaked in `inflight_requests`
and `session_active_requests` with `result: None`, staying renewable and
resolvable to a job expansion had already deleted from the run.

Settle the record (result recorded, out of inflight + every session) exactly
as the completion path would, on all non-dispatch conclusions:
- run cancel (`cancel_run_inner`): use a run-wide `expandable_job_ids` set
  instead of the narrow caller_plans-only collection.
- job cancel (`cancel_job_inner`): settle via `is_expandable_node`.
- submit-time workflow-concurrency overflow cancel (`submit_run_inner`).
- dependency skip / `if:` eval error (`promote_ready_jobs` Skip/Error arm).

Reusable callers mint no records at submit, so retiring them is a no-op;
Settle is idempotent, so overlapping paths cannot double-settle harmfully.

Adds tests covering all four non-dispatch conclusions.
@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cb2e2c85-a87c-4cdc-8d14-ac932ebaa6eb

📥 Commits

Reviewing files that changed from the base of the PR and between ecc20cd and 2842849.

📒 Files selected for processing (2)
  • crates/preloop-runner-server/src/lib_tests.rs
  • crates/preloop-runner-server/src/runtime_scheduling.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The change retires deferred-matrix and reusable-call request correlations during cancellation, queue overflow, dependency skips, and condition failures. Cancellation now preserves recorded terminal statuses. Regression tests cover cleanup and completed caller success.

Changes

Expandable Request Retirement

Layer / File(s) Summary
Retirement helpers and node discovery
crates/preloop-runner-server/src/runtime_scheduling.rs
RequestRetirement is crate-visible. Helpers identify expandable nodes and distinguish settled records from purged records.
Cancellation and queue-overflow settlement
crates/preloop-runner-server/src/runtime_scheduling.rs, crates/preloop-runner-server/src/runs.rs
Run cancellation, job cancellation, and workflow-concurrency queue overflow settle expandable request records with recorded terminal statuses or Cancelled.
Dependency outcomes and regression coverage
crates/preloop-runner-server/src/runtime_scheduling.rs, crates/preloop-runner-server/src/lib_tests.rs
Dependency skips and condition-evaluation errors settle request correlations before concurrency release. Tests cover cleanup, terminal statuses, and preservation of completed caller success.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 28428

This change settles request-correlation records for expandable nodes that conclude without dispatching, preventing stale inflight records on cancellation and dependency-skip paths. No actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description clearly explains the problem, fix, affected paths, tests, and verification. However, it omits the required Protocol surface, Required gates, and Checklist sections, including an explic… Add the missing template sections. Confirm whether the change touches the runner protocol interface, mark each required gate, and complete the checklist for tests, documentation, and changelog impact. Include concrete verification evidence …
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main scheduling change: settling request correlations for expandable nodes on non-dispatch paths.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files. (1 skipped: 1 t…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description clearly explains the problem, fix, affected paths, tests, and verification. However, it omits the required Protocol surface, Required gates, and Checklist sections, including an explicit YES/NO protocol confirmation and gate status.

Resolution

Add the missing template sections. Confirm whether the change touches the runner protocol interface, mark each required gate, and complete the checklist for tests, documentation, and changelog impact. Include concrete verification evidence for the listed commands and tests where applicable.

Full details: Docstring Coverage

Explanation

Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files. (1 skipped: 1 too large.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Bnjoroge/deferred-matrix-correlation

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

@Bnjoroge1
Bnjoroge1 marked this pull request as ready for review August 28, 2026 01:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ecc20cdf37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// `register_expanded_jobs` keeps a deferred node's plan in
// `caller_plans` when the node lives inside a reusable callee; nested
// nodes are found here even after they left every queue.
ids.extend(run.caller_plans.keys().cloned());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve completed caller results during cancellation

When a nested reusable caller has already completed while another job keeps the run active, it remains in run.caller_plans; register_expanded_jobs minted it a job-request record, while propagate_reusable_outputs only terminalizes the run's job status and leaves that request's result unset. Including every caller_plans key here causes a later run or ancestor-caller cancellation to pass this already-successful node to Settle(Cancelled), leaving the persisted request result as Cancelled while run.jobs still says Success. Filter this set to non-terminal nodes, or settle each record with its stored final job status.

Useful? React with 👍 / 👎.

… not hardcoded Cancelled

Codex review (P2): a nested reusable caller that finished Success while the
run stayed active remains in `run.caller_plans` with an unsettled request
record (`register_expanded_jobs` mints it; `propagate_reusable_outputs`
retires none). The run-wide `expandable_job_ids` sweep then passed it to
`Settle(Cancelled)`, corrupting the persisted request result to Cancelled
while `run.jobs` still said Success.

Settle each expandable node with `node_settle_status` — its own verdict from
`run.jobs`, defaulting to Cancelled for a node the run no longer records.
Freshly-cancelled placeholders (already flipped to Cancelled by the status
loop) still settle Cancelled; an already-terminal caller keeps its result.

Also drops the now-redundant narrow `deferred_matrix` settle block in
`cancel_job_inner` that the unified `is_expandable_node` path supersedes.

Adds `cancel_preserves_completed_reusable_caller_result` covering the exact
nested-caller scenario.
@Bnjoroge1

Copy link
Copy Markdown
Collaborator Author

Addressed the Codex P2 (preserve completed caller results) in 2842849.

Root cause confirmed: register_expanded_jobs mints a request record for reusable-caller nodes and keeps them in caller_plans, while propagate_reusable_outputs sets run.jobs[caller] terminal (e.g. Success) but never settles that record. The run-wide expandable_job_ids sweep then stamped Settle(Cancelled), contradicting run.jobs.

Fix: settle each expandable node with node_settle_status (its own verdict from run.jobs, defaulting to Cancelled for a node the run no longer records) instead of a hardcoded Cancelled. Freshly-cancelled placeholders still settle Cancelled; an already-terminal caller keeps its result. Also removed the now-redundant narrow deferred_matrix settle block in cancel_job_inner.

Added cancel_preserves_completed_reusable_caller_result covering the exact nested-caller scenario (fails pre-fix with result=Cancelled).

@Bnjoroge1
Bnjoroge1 merged commit bcd086b into main Aug 28, 2026
11 of 12 checks passed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important

A nested reusable caller can still conclude without dispatching while its request-correlation record remains inflight.

Reviewed changes This review covers the request-correlation settlement changes for expandable scheduling nodes and their regression tests.

  • Cancellation settlement Run and job cancellation now identify expandable nodes across scheduler state and settle their records with the node's existing terminal result.
  • Non-dispatch conclusions Submit-time workflow concurrency overflow and dependency Skip/Error conclusions now retire associated request claims.
  • Regression coverage New integration tests assert settlement for deferred-matrix placeholders and preserve completed reusable-caller results during cancellation.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using GPT Terra | 𝕏

// submit-time request correlation minted for it. Settle
// it like any other terminal conclusion; the node stays
// in the run, so Settle mirrors the completion path.
if job.deferred_matrix.is_some() || job.reusable_call.is_some() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nested reusable callers materialized by register_expanded_jobs have a request record, but a failure or overflow while acquiring their JobSet gates concludes them in the reusable Run arm without retiring it. This cleanup only covers dependency Skip/Error, so those callers remain inflight despite never dispatching.

Technical details
# Settle nested reusable callers rejected by JobSet admission

## Affected sites
- `crates/preloop-runner-server/src/runtime_scheduling.rs:1023-1059` — `caller_jobset_gates` errors and `advance_jobset_admission` errors mark a reusable caller terminal and immediately continue.
- `crates/preloop-runner-server/src/runtime_scheduling.rs:1147` — the added settlement only covers the separate dependency `Skip`/`Error` branch.
- `crates/preloop-runner-server/src/runtime_scheduling.rs:2392-2433` — `register_expanded_jobs` mints records even for a reusable caller materialized inside another reusable workflow.

## Required outcome
- Every nested reusable caller that is concluded by JobSet gate evaluation failure or admission overflow must have its request record settled and removed from inflight/session ownership with the recorded terminal status.
- Add regression coverage for a nested caller whose concurrency expression fails during admission.

Bnjoroge1 added a commit that referenced this pull request Sep 3, 2026
…spatch paths (#189)

* fix(scheduling): settle expandable-node request correlation on non-dispatch paths

MC-3: submit mints a full request-correlation record set for every
non-caller job, including needs-deferred dynamic-matrix nodes. Those nodes
route to expansion and never dispatch to a runner, so no completion, result
patch or disconnect ever settles their records. On every path where such a
node concludes without dispatching, the record leaked in `inflight_requests`
and `session_active_requests` with `result: None`, staying renewable and
resolvable to a job expansion had already deleted from the run.

Settle the record (result recorded, out of inflight + every session) exactly
as the completion path would, on all non-dispatch conclusions:
- run cancel (`cancel_run_inner`): use a run-wide `expandable_job_ids` set
  instead of the narrow caller_plans-only collection.
- job cancel (`cancel_job_inner`): settle via `is_expandable_node`.
- submit-time workflow-concurrency overflow cancel (`submit_run_inner`).
- dependency skip / `if:` eval error (`promote_ready_jobs` Skip/Error arm).

Reusable callers mint no records at submit, so retiring them is a no-op;
Settle is idempotent, so overlapping paths cannot double-settle harmfully.

Adds tests covering all four non-dispatch conclusions.

* fix(scheduling): settle expandable nodes with their concluded status, not hardcoded Cancelled

Codex review (P2): a nested reusable caller that finished Success while the
run stayed active remains in `run.caller_plans` with an unsettled request
record (`register_expanded_jobs` mints it; `propagate_reusable_outputs`
retires none). The run-wide `expandable_job_ids` sweep then passed it to
`Settle(Cancelled)`, corrupting the persisted request result to Cancelled
while `run.jobs` still said Success.

Settle each expandable node with `node_settle_status` — its own verdict from
`run.jobs`, defaulting to Cancelled for a node the run no longer records.
Freshly-cancelled placeholders (already flipped to Cancelled by the status
loop) still settle Cancelled; an already-terminal caller keeps its result.

Also drops the now-redundant narrow `deferred_matrix` settle block in
`cancel_job_inner` that the unified `is_expandable_node` path supersedes.

Adds `cancel_preserves_completed_reusable_caller_result` covering the exact
nested-caller scenario.
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