fix: recover failed task launches safely (#3437)
This commit is contained in:
@@ -4,7 +4,7 @@ system: tasks
|
||||
requirements:
|
||||
- REQ-TASKS-TASK-LAUNCH-FAILURE-RECOVERY-001
|
||||
created: 2026-08-24
|
||||
updated: 2026-09-03
|
||||
updated: 2026-09-06
|
||||
owners:
|
||||
- cfl12
|
||||
---
|
||||
@@ -45,8 +45,20 @@ actions, exact task-repository identity when applicable, and an idempotency
|
||||
stamp.
|
||||
|
||||
The supported categories are `base_branch_missing`, `pr_already_closed`,
|
||||
`default_branch_unresolved`, and `generic_launch_failure`. The supported
|
||||
actions are `retry_default`, `pick_base_branch`, and `mark_review_done`.
|
||||
`default_branch_unresolved`, `workspace_checkout_failed`, and
|
||||
`generic_launch_failure`. The supported actions are `retry_launch`,
|
||||
`retry_default`, `pick_base_branch`, and `mark_review_done`.
|
||||
|
||||
The failure category controls the available actions. A repository identity
|
||||
alone does not make a base-branch action valid.
|
||||
|
||||
| Category | Valid actions |
|
||||
| --- | --- |
|
||||
| `base_branch_missing` | `retry_default`, `pick_base_branch` |
|
||||
| `default_branch_unresolved` | `pick_base_branch` |
|
||||
| `workspace_checkout_failed` | `retry_launch` |
|
||||
| `pr_already_closed` | `mark_review_done` when the workflow permits it |
|
||||
| `generic_launch_failure` | `retry_launch` |
|
||||
|
||||
The `TaskStatusSummary.active_error` projection selects the newest active
|
||||
record, limits strings and actions, removes duplicate actions without changing
|
||||
@@ -64,6 +76,8 @@ the current error stamp; stale stamps fail without mutation.
|
||||
relaunches.
|
||||
- `pick_base_branch` validates and persists one selected branch before
|
||||
relaunch.
|
||||
- `retry_launch` keeps the task-repository settings unchanged and repeats the
|
||||
failed launch. It still requires the current error stamp.
|
||||
- `mark_review_done` is allowed only for a valid terminal workflow step and
|
||||
when every relevant PR is terminal; it uses the normal task-move service.
|
||||
|
||||
@@ -106,6 +120,32 @@ but `retry_default` and `pick_base_branch` must write the resolved base to
|
||||
the exact `task_repositories` row and that write must succeed before relaunch.
|
||||
No new table is required.
|
||||
|
||||
## Pull-request checkout isolation
|
||||
|
||||
A pull-request head uses a Kandev-owned ref that includes the pull-request
|
||||
number, specifically `refs/kandev/pull/<N>/head`. The fetch can force-update
|
||||
this internal ref because users do not own it. Ordinary remote refresh and
|
||||
pruning do not manage this namespace. The fetch never writes directly to a
|
||||
user-named local branch.
|
||||
|
||||
The worktree manager verifies the fetched ref before it selects a start point.
|
||||
If the named local branch has unrelated history, the manager preserves that
|
||||
branch. It creates a unique task branch from the verified pull-request ref.
|
||||
|
||||
This rule also applies when two pull requests reuse the same source-branch
|
||||
name. A local branch from the first pull request cannot block the second pull
|
||||
request. A fallback branch uses a task-owned deterministic suffix and a
|
||||
bounded retry sequence, so an existing fallback branch cannot make launch fail
|
||||
because of one random-name collision.
|
||||
|
||||
The manager sets `origin/<source-branch>` as upstream only when that ref points
|
||||
to the verified pull-request start point. A remote branch with different
|
||||
history is never attached as the worktree upstream.
|
||||
|
||||
If Kandev cannot fetch or verify the pull-request ref, preparation fails with a
|
||||
typed `workspace_checkout_failed` error. The error retains the existing
|
||||
credential and path redaction rules.
|
||||
|
||||
## Failure and security
|
||||
|
||||
PR lookup failures launch normally. Remote-default timeout, authentication,
|
||||
@@ -117,12 +157,49 @@ Initial-prompt errors use a safe generic durable message and the same
|
||||
stale-event checks as other agent failures. Raw attachment paths and provider
|
||||
details remain in backend diagnostics and do not reach the durable projection.
|
||||
|
||||
## Launch-error presentation ownership
|
||||
|
||||
The active `TaskStatusSummary.active_error` record owns the primary launch
|
||||
error for its matching session and stamp. A task-wide record remains visible
|
||||
while a prior session is selected, but it owns session surfaces only when no
|
||||
session is selected. A session-owned record owns surfaces only for its exact
|
||||
session and stamp. The task Chat surface renders one persistent card from this
|
||||
record.
|
||||
|
||||
The matching launch error suppresses these secondary presentations:
|
||||
|
||||
- The previous-agent-error notice.
|
||||
- The standalone failed preparation row.
|
||||
- The empty-turn warning.
|
||||
- The generic failed-agent status row.
|
||||
- The stopped-session recovery banner.
|
||||
- A launch-error toast while the user views the affected task.
|
||||
|
||||
Only synthetic rows generated by the active launch failure are suppressed;
|
||||
stale stamped or historical launch-only rows remain visible. An unrelated
|
||||
historical error keeps its normal transcript presentation. A runtime error that
|
||||
occurs after agent startup keeps the existing session
|
||||
recovery surface.
|
||||
|
||||
The card shows a category title, a short cause, a no-change statement, and only
|
||||
valid recovery actions. One disclosure shows bounded technical details. The
|
||||
disclosure does not show raw Git output, credentials, or local file paths.
|
||||
|
||||
A failed recovery updates the same card. It does not add another banner or
|
||||
toast. A successful recovery removes the card after launch succeeds.
|
||||
|
||||
## Responsive presentation
|
||||
|
||||
The task Chat surface renders one persistent error card from the shared
|
||||
projection. Desktop actions are inline; mobile actions wrap and branch
|
||||
selection uses the existing mobile picker. Both surfaces use the same action
|
||||
authorization and remain free of horizontal overflow.
|
||||
The task Chat surface is the entry point on desktop and phone. The card remains
|
||||
inline because the error and its actions belong to the current task.
|
||||
|
||||
Desktop actions use one compact row. Phone actions use a vertical layout with
|
||||
44-pixel touch targets. The existing mobile picker remains the branch-selection
|
||||
surface for `base_branch_missing` and `default_branch_unresolved`.
|
||||
|
||||
The transcript owns vertical scrolling. Expanded technical details wrap inside
|
||||
the card and do not create a second scroll region. Both layouts prevent
|
||||
document-level horizontal overflow.
|
||||
|
||||
`useEnsureTaskSession` keeps one request latch after an error. Store updates and
|
||||
loader identity changes do not start another request. The Retry control starts
|
||||
|
||||
Reference in New Issue
Block a user