diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2518,13 +2518,15 @@
     'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
     'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php',
     'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php',
+    'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php',
     'PhabricatorWorkerManagementFloodWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php',
+    'PhabricatorWorkerManagementFreeWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php',
+    'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php',
     'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
     'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
     'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php',
     'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php',
     'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php',
-    'PhabricatorWorkerTaskUpdateController' => 'applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php',
     'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php',
     'PhabricatorWorkerYieldException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php',
     'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php',
@@ -5702,13 +5704,15 @@
     'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
     'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
     'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery',
+    'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementFloodWorkflow' => 'PhabricatorWorkerManagementWorkflow',
+    'PhabricatorWorkerManagementFreeWorkflow' => 'PhabricatorWorkerManagementWorkflow',
+    'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorWorkerPermanentFailureException' => 'Exception',
     'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
-    'PhabricatorWorkerTaskUpdateController' => 'PhabricatorDaemonController',
     'PhabricatorWorkerTestCase' => 'PhabricatorTestCase',
     'PhabricatorWorkerYieldException' => 'Exception',
     'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase',
diff --git a/src/applications/daemon/application/PhabricatorDaemonsApplication.php b/src/applications/daemon/application/PhabricatorDaemonsApplication.php
--- a/src/applications/daemon/application/PhabricatorDaemonsApplication.php
+++ b/src/applications/daemon/application/PhabricatorDaemonsApplication.php
@@ -41,8 +41,6 @@
       '/daemon/' => array(
         '' => 'PhabricatorDaemonConsoleController',
         'task/(?P<id>[1-9]\d*)/' => 'PhabricatorWorkerTaskDetailController',
-        'task/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
-          => 'PhabricatorWorkerTaskUpdateController',
         'log/' => array(
           '' => 'PhabricatorDaemonLogListController',
           '(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',
diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
--- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
+++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
@@ -38,8 +38,7 @@
           $task->getID(),
           $task->getTaskClass()));
 
-      $actions = $this->buildActionListView($task);
-      $properties = $this->buildPropertyListView($task, $actions);
+      $properties = $this->buildPropertyListView($task);
 
       $object_box = id(new PHUIObjectBoxView())
         ->setHeader($header)
@@ -74,57 +73,12 @@
       ));
   }
 
-  private function buildActionListView(PhabricatorWorkerTask $task) {
-    $request = $this->getRequest();
-    $user = $request->getUser();
-    $id = $task->getID();
-
-    $view = id(new PhabricatorActionListView())
-      ->setUser($user)
-      ->setObjectURI($request->getRequestURI());
-
-    if ($task->isArchived()) {
-      $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
-      $can_retry = ($task->getResult() != $result_success);
-
-      $view->addAction(
-        id(new PhabricatorActionView())
-          ->setName(pht('Retry Task'))
-          ->setHref($this->getApplicationURI('/task/'.$id.'/retry/'))
-          ->setIcon('fa-refresh')
-          ->setWorkflow(true)
-          ->setDisabled(!$can_retry));
-    } else {
-      $view->addAction(
-        id(new PhabricatorActionView())
-          ->setName(pht('Cancel Task'))
-          ->setHref($this->getApplicationURI('/task/'.$id.'/cancel/'))
-          ->setIcon('fa-times')
-          ->setWorkflow(true));
-    }
-
-    $can_release = (!$task->isArchived()) &&
-                   ($task->getLeaseOwner());
-
-    $view->addAction(
-      id(new PhabricatorActionView())
-        ->setName(pht('Free Lease'))
-        ->setHref($this->getApplicationURI('/task/'.$id.'/release/'))
-        ->setIcon('fa-unlock')
-        ->setWorkflow(true)
-        ->setDisabled(!$can_release));
-
-    return $view;
-  }
-
   private function buildPropertyListView(
-    PhabricatorWorkerTask $task,
-    PhabricatorActionListView $actions) {
+    PhabricatorWorkerTask $task) {
 
     $viewer = $this->getRequest()->getUser();
 
     $view = new PHUIPropertyListView();
-    $view->setActionList($actions);
 
     if ($task->isArchived()) {
       switch ($task->getResult()) {
diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php
deleted file mode 100644
--- a/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-final class PhabricatorWorkerTaskUpdateController
-  extends PhabricatorDaemonController {
-
-  private $id;
-  private $action;
-
-  public function willProcessRequest(array $data) {
-    $this->id = $data['id'];
-    $this->action = $data['action'];
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $user = $request->getUser();
-
-    $task = id(new PhabricatorWorkerActiveTask())->load($this->id);
-    if (!$task) {
-      $task = id(new PhabricatorWorkerArchiveTask())->load($this->id);
-    }
-
-    if (!$task) {
-      return new Aphront404Response();
-    }
-
-    $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
-    $can_retry = ($task->isArchived()) &&
-                 ($task->getResult() != $result_success);
-
-    $can_cancel = !$task->isArchived();
-    $can_release = (!$task->isArchived()) &&
-                   ($task->getLeaseOwner());
-
-    $next_uri = $this->getApplicationURI('/task/'.$task->getID().'/');
-
-    if ($request->isFormPost()) {
-      switch ($this->action) {
-        case 'retry':
-          if ($can_retry) {
-            $task->unarchiveTask();
-          }
-          break;
-        case 'cancel':
-          if ($can_cancel) {
-            // Forcibly break the lease if one exists, so we can archive the
-            // task.
-            $task->setLeaseOwner(null);
-            $task->setLeaseExpires(time());
-
-            $task->archiveTask(
-              PhabricatorWorkerArchiveTask::RESULT_CANCELLED,
-              0);
-          }
-          break;
-        case 'release':
-          if ($can_release) {
-            $task->setLeaseOwner(null);
-            $task->setLeaseExpires(time());
-            $task->save();
-          }
-          break;
-      }
-      return id(new AphrontRedirectResponse())
-        ->setURI($next_uri);
-    }
-
-    $dialog = new AphrontDialogView();
-    $dialog->setUser($user);
-
-    switch ($this->action) {
-      case 'retry':
-        if ($can_retry) {
-          $dialog->setTitle(pht('Really retry task?'));
-          $dialog->appendChild(phutil_tag('p', array(), pht(
-            'The task will be put back in the queue and executed again.')));
-          $dialog->addSubmitButton('Retry Task');
-        } else {
-          $dialog->setTitle(pht('Can Not Retry'));
-          $dialog->appendChild(phutil_tag('p', array(), pht(
-            'Only archived, unsuccessful tasks can be retried.')));
-        }
-        break;
-      case 'cancel':
-        if ($can_cancel) {
-          $dialog->setTitle(pht('Really cancel task?'));
-          $dialog->appendChild(phutil_tag('p', array(), pht(
-            'The work this task represents will never be performed if you '.
-            'cancel it. Are you sure you want to cancel it?')));
-          $dialog->addSubmitButton(pht('Cancel Task'));
-        } else {
-          $dialog->setTitle(pht('Cannot Cancel'));
-          $dialog->appendChild(phutil_tag('p', array(), pht(
-            'Only active tasks can be cancelled.')));
-        }
-        break;
-      case 'release':
-        if ($can_release) {
-          $dialog->setTitle(pht('Really free task lease?'));
-          $dialog->appendChild(phutil_tag('p', array(), pht(
-            'If the process which owns the task lease is still doing work '.
-            'on it, the work may be performed twice. Are you sure you '.
-            'want to free the lease?')));
-          $dialog->addSubmitButton(pht('Free Lease'));
-        } else {
-          $dialog->setTitle(pht('Cannot Free Lease'));
-          $dialog->appendChild(phutil_tag('p', array(), pht(
-            'Only active, leased tasks may have their leases freed.')));
-        }
-        break;
-      default:
-        return new Aphront404Response();
-    }
-
-    $dialog->addCancelButton($next_uri);
-
-    return id(new AphrontDialogResponse())->setDialog($dialog);
-  }
-
-}
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php
@@ -0,0 +1,52 @@
+<?php
+
+final class PhabricatorWorkerManagementCancelWorkflow
+  extends PhabricatorWorkerManagementWorkflow {
+
+  public function didConstruct() {
+    $this
+      ->setName('cancel')
+      ->setExamples('**cancel** --id __id__')
+      ->setSynopsis(
+        pht(
+          'Cancel selected tasks. The work these tasks represent will never '.
+          'be performed.'))
+      ->setArguments($this->getTaskSelectionArguments());
+  }
+
+  public function execute(PhutilArgumentParser $args) {
+    $console = PhutilConsole::getConsole();
+    $tasks = $this->loadTasks($args);
+
+    foreach ($tasks as $task) {
+      $can_cancel = !$task->isArchived();
+      if (!$can_cancel) {
+        $console->writeOut(
+          "**<bg:yellow> %s </bg>** %s\n",
+          pht('ARCHIVED'),
+          pht(
+            '%s is already archived, and can not be cancelled.',
+            $this->describeTask($task)));
+        continue;
+      }
+
+      // Forcibly break the lease if one exists, so we can archive the
+      // task.
+      $task->setLeaseOwner(null);
+      $task->setLeaseExpires(PhabricatorTime::getNow());
+      $task->archiveTask(
+        PhabricatorWorkerArchiveTask::RESULT_CANCELLED,
+        0);
+
+      $console->writeOut(
+        "**<bg:green> %s </bg>** %s\n",
+        pht('CANCELLED'),
+        pht(
+          '%s was cancelled.',
+          $this->describeTask($task)));
+    }
+
+    return 0;
+  }
+
+}
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php
@@ -0,0 +1,58 @@
+<?php
+
+final class PhabricatorWorkerManagementFreeWorkflow
+  extends PhabricatorWorkerManagementWorkflow {
+
+  public function didConstruct() {
+    $this
+      ->setName('free')
+      ->setExamples('**free** --id __id__')
+      ->setSynopsis(
+        pht(
+          'Free leases on selected tasks. If the daemon holding the lease is '.
+          'still working on the task, this may cause the task to execute '.
+          'twice.'))
+      ->setArguments($this->getTaskSelectionArguments());
+  }
+
+  public function execute(PhutilArgumentParser $args) {
+    $console = PhutilConsole::getConsole();
+    $tasks = $this->loadTasks($args);
+
+    foreach ($tasks as $task) {
+      if ($task->isArchived()) {
+        $console->writeOut(
+          "**<bg:yellow> %s </bg>** %s\n",
+          pht('ARCHIVED'),
+          pht(
+            '%s is archived; archived tasks do not have leases.',
+            $this->describeTask($task)));
+        continue;
+      }
+
+      if ($task->getLeaseOwner() === null) {
+        $console->writeOut(
+          "**<bg:yellow> %s </bg>** %s\n",
+          pht('FREE'),
+          pht(
+            '%s has no active lease.',
+            $this->describeTask($task)));
+        continue;
+      }
+
+      $task->setLeaseOwner(null);
+      $task->setLeaseExpires(PhabricatorTime::getNow());
+      $task->save();
+
+      $console->writeOut(
+        "**<bg:green> %s </bg>** %s\n",
+        pht('LEASE FREED'),
+        pht(
+          '%s was freed from its lease.',
+          $this->describeTask($task)));
+    }
+
+    return 0;
+  }
+
+}
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php
@@ -0,0 +1,57 @@
+<?php
+
+final class PhabricatorWorkerManagementRetryWorkflow
+  extends PhabricatorWorkerManagementWorkflow {
+
+  public function didConstruct() {
+    $this
+      ->setName('retry')
+      ->setExamples('**retry** --id __id__')
+      ->setSynopsis(
+        pht(
+          'Retry selected tasks which previously failed permanently or '.
+          'were cancelled. Only archived, unsuccessful tasks can be '.
+          'retried.'))
+      ->setArguments($this->getTaskSelectionArguments());
+  }
+
+  public function execute(PhutilArgumentParser $args) {
+    $console = PhutilConsole::getConsole();
+    $tasks = $this->loadTasks($args);
+
+    foreach ($tasks as $task) {
+      if (!$task->isArchived()) {
+        $console->writeOut(
+          "**<bg:yellow> %s </bg>** %s\n",
+          pht('ACTIVE'),
+          pht(
+            '%s is already in the active task queue.',
+            $this->describeTask($task)));
+        continue;
+      }
+
+      $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
+      if ($task->getResult() == $result_success) {
+        $console->writeOut(
+          "**<bg:yellow> %s </bg>** %s\n",
+          pht('SUCCEEDED'),
+          pht(
+            '%s has already succeeded, and can not be retried.',
+            $this->describeTask($task)));
+        continue;
+      }
+
+      $task->unarchiveTask();
+
+      $console->writeOut(
+        "**<bg:green> %s </bg>** %s\n",
+        pht('QUEUED'),
+        pht(
+          '%s was queued for retry.',
+          $this->describeTask($task)));
+    }
+
+    return 0;
+  }
+
+}
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php
--- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php
@@ -1,4 +1,49 @@
 <?php
 
 abstract class PhabricatorWorkerManagementWorkflow
-  extends PhabricatorManagementWorkflow {}
+  extends PhabricatorManagementWorkflow {
+
+  protected function getTaskSelectionArguments() {
+    return array(
+      array(
+        'name' => 'id',
+        'param' => 'id',
+        'repeat' => true,
+        'help' => pht('Select one or more tasks by ID.'),
+      ),
+    );
+  }
+
+  protected function loadTasks(PhutilArgumentParser $args) {
+    $ids = $args->getArg('id');
+    if (!$ids) {
+      throw new PhutilArgumentUsageException(
+        pht('Use --id to select tasks by ID.'));
+    }
+
+    $active_tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
+      'id IN (%Ls)',
+      $ids);
+    $archive_tasks = id(new PhabricatorWorkerArchiveTask())->loadAllWhere(
+      'id IN (%Ls)',
+      $ids);
+
+    $tasks =
+      mpull($active_tasks, null, 'getID') +
+      mpull($archive_tasks, null, 'getID');
+
+    foreach ($ids as $id) {
+      if (empty($tasks[$id])) {
+        throw new PhutilArgumentUsageException(
+          pht('No task exists with id "%s"!', $id));
+      }
+    }
+
+    return $tasks;
+  }
+
+  protected function describeTask(PhabricatorWorkerTask $task) {
+    return pht('Task %d (%s)', $task->getID(), $task->getTaskClass());
+  }
+
+}