TaskFlow: add managed child task execution#59610
Conversation
Greptile SummaryThis PR adds managed child task spawning to the TaskFlow substrate: Key changes and observations:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/tasks/task-executor.ts
Line: 389-397
Comment:
**Duplicated helpers also present in `task-registry.ts`**
`isActiveTaskStatus` (lines 389–391) and `isTerminalFlowStatus` (lines 393–397) are defined verbatim in both `task-executor.ts` and `task-registry.ts` (lines ~83 and ~100 respectively). If the definition of "active" or "terminal" ever needs to change, both copies must be updated in sync.
These should either live in a shared utility (e.g., `task-status-helpers.ts`) and imported into both modules, or one module should import them from the other. As-is, a change to one copy won't automatically propagate.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/tasks/task-executor.ts
Line: 452-484
Comment:
**`owner_key_mismatch` and `scope_kind_not_session` errors are silently re-thrown**
`mapRunTaskInFlowCreateError` handles `cancel_requested`, `terminal`, and `parent_flow_not_found` codes, but falls through to `throw params.error` for `owner_key_mismatch` and `scope_kind_not_session`.
In the current call site (`runTaskInFlow`) these codes are unreachable because `ownerKey` is always set from `flow.ownerKey` and `scopeKind` is hardcoded to `"session"`. However, if a future call site ever creates a task with different params and routes through this helper, the raw `ParentFlowLinkError` would escape as an unhandled exception instead of returning a structured `RunTaskInFlowResult`.
Adding explicit branches (or at minimum a developer comment marking them as intentionally unreachable) would make the exhaustiveness contract clear to future maintainers:
```typescript
// scope_kind_not_session and owner_key_mismatch are unreachable from runTaskInFlow
// because scopeKind is always "session" and ownerKey is always flow.ownerKey.
// Re-throw as a programmer error if somehow reached.
throw params.error;
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Changelog: update TaskFlow PR reference" | Re-trigger Greptile |
| @@ -386,6 +396,237 @@ function isTerminalFlowStatus(status: FlowRecord["status"]): boolean { | |||
| ); | |||
| } | |||
There was a problem hiding this comment.
Duplicated helpers also present in
task-registry.ts
isActiveTaskStatus (lines 389–391) and isTerminalFlowStatus (lines 393–397) are defined verbatim in both task-executor.ts and task-registry.ts (lines ~83 and ~100 respectively). If the definition of "active" or "terminal" ever needs to change, both copies must be updated in sync.
These should either live in a shared utility (e.g., task-status-helpers.ts) and imported into both modules, or one module should import them from the other. As-is, a change to one copy won't automatically propagate.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/tasks/task-executor.ts
Line: 389-397
Comment:
**Duplicated helpers also present in `task-registry.ts`**
`isActiveTaskStatus` (lines 389–391) and `isTerminalFlowStatus` (lines 393–397) are defined verbatim in both `task-executor.ts` and `task-registry.ts` (lines ~83 and ~100 respectively). If the definition of "active" or "terminal" ever needs to change, both copies must be updated in sync.
These should either live in a shared utility (e.g., `task-status-helpers.ts`) and imported into both modules, or one module should import them from the other. As-is, a change to one copy won't automatically propagate.
How can I resolve this? If you propose a fix, please make it concise.| function mapRunTaskInFlowCreateError(params: { | ||
| error: unknown; | ||
| flowId: string; | ||
| }): RunTaskInFlowResult { | ||
| const flow = getFlowById(params.flowId); | ||
| if (isParentFlowLinkError(params.error)) { | ||
| if (params.error.code === "cancel_requested") { | ||
| return { | ||
| found: true, | ||
| created: false, | ||
| reason: "Flow cancellation has already been requested.", | ||
| ...(flow ? { flow } : {}), | ||
| }; | ||
| } | ||
| if (params.error.code === "terminal") { | ||
| const terminalStatus = flow?.status ?? params.error.details?.status ?? "terminal"; | ||
| return { | ||
| found: true, | ||
| created: false, | ||
| reason: `Flow is already ${terminalStatus}.`, | ||
| ...(flow ? { flow } : {}), | ||
| }; | ||
| } | ||
| if (params.error.code === "parent_flow_not_found") { | ||
| return { | ||
| found: false, | ||
| created: false, | ||
| reason: "Flow not found.", | ||
| }; | ||
| } | ||
| } | ||
| throw params.error; | ||
| } |
There was a problem hiding this comment.
owner_key_mismatch and scope_kind_not_session errors are silently re-thrown
mapRunTaskInFlowCreateError handles cancel_requested, terminal, and parent_flow_not_found codes, but falls through to throw params.error for owner_key_mismatch and scope_kind_not_session.
In the current call site (runTaskInFlow) these codes are unreachable because ownerKey is always set from flow.ownerKey and scopeKind is hardcoded to "session". However, if a future call site ever creates a task with different params and routes through this helper, the raw ParentFlowLinkError would escape as an unhandled exception instead of returning a structured RunTaskInFlowResult.
Adding explicit branches (or at minimum a developer comment marking them as intentionally unreachable) would make the exhaustiveness contract clear to future maintainers:
// scope_kind_not_session and owner_key_mismatch are unreachable from runTaskInFlow
// because scopeKind is always "session" and ownerKey is always flow.ownerKey.
// Re-throw as a programmer error if somehow reached.
throw params.error;Prompt To Fix With AI
This is a comment left during a code review.
Path: src/tasks/task-executor.ts
Line: 452-484
Comment:
**`owner_key_mismatch` and `scope_kind_not_session` errors are silently re-thrown**
`mapRunTaskInFlowCreateError` handles `cancel_requested`, `terminal`, and `parent_flow_not_found` codes, but falls through to `throw params.error` for `owner_key_mismatch` and `scope_kind_not_session`.
In the current call site (`runTaskInFlow`) these codes are unreachable because `ownerKey` is always set from `flow.ownerKey` and `scopeKind` is hardcoded to `"session"`. However, if a future call site ever creates a task with different params and routes through this helper, the raw `ParentFlowLinkError` would escape as an unhandled exception instead of returning a structured `RunTaskInFlowResult`.
Adding explicit branches (or at minimum a developer comment marking them as intentionally unreachable) would make the exhaustiveness contract clear to future maintainers:
```typescript
// scope_kind_not_session and owner_key_mismatch are unreachable from runTaskInFlow
// because scopeKind is always "session" and ownerKey is always flow.ownerKey.
// Re-throw as a programmer error if somehow reached.
throw params.error;
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f2abfc424
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (isTerminalFlowStatus(flow.status)) { | ||
| return { | ||
| found: true, | ||
| cancelled: false, | ||
| reason: `Flow is already ${flow.status}.`, |
There was a problem hiding this comment.
Cancel active child tasks even when flow is terminal
This early terminal-status return skips the child-cancellation loop entirely, so cancelFlowById no longer cleans up queued/running child tasks when flow/task state is inconsistent (for example after partial restore or prior failed updates). Before this change, cancellation still attempted cancelTaskById first, which gave operators a recovery path; now the call exits with "Flow is already ..." and leaves active children running.
Useful? React with 👍 / 👎.
| if ("reason" in cancelRequestedFlow) { | ||
| return { | ||
| found: true, | ||
| cancelled: false, | ||
| reason: cancelRequestedFlow.reason, |
There was a problem hiding this comment.
Retry cancel-intent write on flow revision conflicts
If requestFlowCancel returns a revision conflict, this branch returns immediately and never reaches the loop that cancels active child tasks. That means a concurrent metadata/state update on the flow can turn a cancel request into a no-op until someone retries manually, even though children are still running. The cancel path should reconcile with the latest revision (or retry) instead of aborting before task cancellation.
Useful? React with 👍 / 👎.
15d453b to
498779a
Compare
498779a to
e6cdde6
Compare
|
Merged via squash.
Thanks @mbelinky! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6cdde6c21
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| found: true, | ||
| cancelled: false, | ||
| reason: "One or more child tasks are still active.", | ||
| flow: getFlowById(flow.flowId), | ||
| flow: getFlowById(flow.flowId) ?? cancelRequestedFlow, |
There was a problem hiding this comment.
Treat accepted flow-cancel requests as successful
This branch reports cancelled: false even after cancelRequestedAt has already been persisted, so callers cannot distinguish “cancel request accepted, waiting for child shutdown” from a true failure. In this repo, flowsCancelCommand exits with an error whenever cancelled is false, which means operators see a failed cancel even though scheduling is already blocked and the flow is now in sticky-cancel mode.
Useful? React with 👍 / 👎.
Summary
Supersedes
mainWhat This PR Does
This is the first execution layer on top of the restored TaskFlow substrate.
Before this PR, TaskFlow could exist as durable state, but a managed TaskFlow could not yet launch child tasks under itself in a first-class way.
After this PR, a managed TaskFlow can spawn child tasks, and cancellation behaves like a real parent-job cancel: mark stop intent first, then shut down active children, then settle the TaskFlow once those children finish.
This PR adds:
runTaskInFlow(...)/ owner-scoped spawning for managed TaskFlowscancelRequestedAtsemantics before child shutdowncancelledonce the last active child settlesWhat This PR Does Not Do
Tasks vs TaskFlow
Validation
mb-server:pnpm buildmb-server: focused Vitest slice across flow/task/doctor surfaces