Skip to content

feat: make coder task send resumes paused tasks - #22203

Merged
DanielleMaywood merged 14 commits into
mainfrom
danielle/coco-22-tasks-cli-send-auto-resumes-paused-tasks
Mar 7, 2026
Merged

DanielleMaywood merged 14 commits into
mainfrom
danielle/coco-22-tasks-cli-send-auto-resumes-paused-tasks

Conversation

@DanielleMaywood

@DanielleMaywood DanielleMaywood commented Feb 19, 2026 •

Copy link
Copy Markdown
Contributor

Closes COCO-22 / Closes coder/internal#1266

The test for this was a little awkward to write, so a little refactoring was done. Happy to have another go if we think they're a little unwieldy.

@DanielleMaywood
DanielleMaywood force-pushed the danielle/coco-22-tasks-cli-send-auto-resumes-paused-tasks branch from a9dc59d to 065118a Compare February 19, 2026 23:50
@DanielleMaywood DanielleMaywood changed the title feat: coder task send resumes when task is paused feat: coder task send resumes paused tasks Feb 19, 2026
@DanielleMaywood
DanielleMaywood force-pushed the danielle/coco-22-tasks-cli-send-auto-resumes-paused-tasks branch from 065118a to cbdf745 Compare February 20, 2026 09:33
@DanielleMaywood
DanielleMaywood marked this pull request as ready for review February 20, 2026 10:24
@coderagents

coderagents Bot commented Feb 20, 2026 •

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • cli/task_send.go (Long description) → docs/reference/cli/task_send.md - The coder task send command now auto-resumes paused tasks and waits for initializing tasks before sending input. This behavioral change is not reflected in the command's Long description/examples. Consider adding an example or note, e.g.: "If the task is paused, it will be automatically resumed before input is sent." Since docs/reference/cli/task_send.md is auto-generated, the update must be made to the Long field in cli/task_send.go.

Automated review via Coder Tasks

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good in general, but caught a potential UX issue we haven't considered.

Comment thread cli/task_send.go Resolved
Comment thread cli/task_send.go Resolved
@DanielleMaywood DanielleMaywood changed the title feat: coder task send resumes paused tasks feat: coder task send will resumes paused tasks Feb 20, 2026
@DanielleMaywood DanielleMaywood changed the title feat: coder task send will resumes paused tasks feat: make coder task send resumes paused tasks Feb 20, 2026

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we still need a few adjustments here to avoid bad UX, wdyt?

Comment thread cli/task_send.go Resolved
Comment thread cli/task_send.go Resolved
Comment thread cli/task_send.go Outdated
if err != nil {
return xerrors.Errorf("get task by id: %w", err)
} else if task.Status == codersdk.TaskStatusError || task.Status == codersdk.TaskStatusUnknown {
return xerrors.Errorf("task entered %s state while waiting for it to become active", task.Status)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've unfortunately observed that error might pop up ephemerally during startup (I'm assuming it's the agentapi health check that fails long enough on startup that this happens):

Image

I'm not sure what the best options here would be. Give a grace-period for the status to change? Hopefully something we can fix on the API/module side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Roughly how long of a grace period should we give (based on your observations)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe 1-2 minutes is fine, but I'd still say 5 just in case. It really depends on how long the AI agent module takes to set itself up and what the agentapi health check grace period is set to (in this case, it was 63s as per agentapi module).

The template I'm using builds agentapi from a custom branch, so it's possible that pushed it just over the 63s limit and became unhealthy.

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Caught a few more states we need to handle but otherwise I think this is looking good.

Comment thread cli/task_send.go
// When we have a streaming Task API, this should be converted away from polling.

ticker := time.NewTicker(time.Second)
// TODO(DanielleMaywood):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: What's the todo, to remove this once we have a better way to detect non-error states during startup?

Comment thread cli/task_send.go
}

if task.Status == codersdk.TaskStatusActive {
switch task.Status {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we need to handle all states here, consider if the task gets paused in-between the checks. We all never go to error or unknown and active also don't be reached.

Comment thread cli/task_send.go Outdated
return xerrors.Errorf("task has completed and cannot be sent input")
case codersdk.TaskStateFailed:
return xerrors.Errorf("task has failed and cannot be sent input")
case codersdk.TaskStateWorking:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At least theoretically, a task could be paused and this state will remain working indefinitely.

Image

- Use backtick string for Long description to avoid escaped quotes.
- Handle all states in waitForTaskIdle: add default branch that
  returns an error for unexpected app states (e.g. task paused
  externally) instead of polling indefinitely.
- Handle all task statuses in waitForTaskReady: give Paused,
  Pending, and Unknown their own explicit cases instead of grouping
  Paused with Unknown.
- Clarify TODO comment on the error grace period to explain it
  should be removed once the upstream agentapi health check no
  longer reports transient errors during startup.
- Add test for waitForTaskReady detecting a task paused mid-poll.
- Add test for waitForTaskIdle polling through working app state.
@DanielleMaywood
DanielleMaywood requested a review from mafredri March 6, 2026 12:20
Comment thread cli/task_send.go Outdated
if err != nil {
return xerrors.Errorf("get task by id: %w", err)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems I conveyed this poorly, but in my previous review, but my intent was that we repeat the waitForTaskReady logic here (i.e. extract it into a shared utility function). That way if the task suddenly becomes paused (or similar) after we've escaped waitForTaskReady, we don't end up looping forever (CurrentState may remain nil).

Actually, an inversion of this logic seems logical, waitForTaskReady (can be renamed) calls waitForTaskIdle which doesn't have any ticker or for-loop. Simple check. That way we can re-use the single grace-period in case the task goes into error state again at a later time.

Merges the two sequential polling functions into a single
waitForTaskIdle that handles both build-watching and idle-polling.
This prevents status changes (e.g. paused) from being missed between
the two phases and avoids polling forever when CurrentState is nil
during the transition.

@mafredri mafredri left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The core logic is pretty good now. But I found two more issues:

  1. No indication after workspace build of what we're doing
  2. CurrentState == nil check will never resolve because because the agent isn't reporting an update on resume, and I suppose the MCP either isn't running or isn't reporting the initial idle state either 🤔
== ✔ Starting workspace [4826ms]
==> ⧗ Cleaning Up
=== ✔ Cleaning Up [46ms]
[stuck here for a long time, no updates, task ready, current state == nil]

Print "Waiting for task to become idle..." after the workspace build
completes so users know what the CLI is doing.

Add a 30s grace period for Active tasks with nil CurrentState. On
resume the MCP may never report an initial app status, causing the
polling loop to hang indefinitely. After the grace period expires
we treat nil as idle and proceed with sending input.

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So much grace in this PR! 🙏

@DanielleMaywood
DanielleMaywood merged commit 4cf8d44 into main Mar 7, 2026
26 checks passed
@DanielleMaywood
DanielleMaywood deleted the danielle/coco-22-tasks-cli-send-auto-resumes-paused-tasks branch March 7, 2026 01:36
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tasks: CLI send auto-resumes paused tasks

2 participants