diff --git a/.devcontainer/application/Dockerfile b/.devcontainer/application/Dockerfile
--- a/.devcontainer/application/Dockerfile
+++ b/.devcontainer/application/Dockerfile
@@ -40,6 +40,7 @@
 ADD /config/fastcgi.conf /etc/nginx/
 ADD /config/php-fpm.conf /etc/php/8.2/fpm/
 ADD /config/php.ini /etc/php/8.2/fpm/
+ADD /config/php.ini /etc/php/8.2/cli/
 ADD /config/aphlict.phorge.json /install_scripts/
 
 # == Add Supervisord config files ==
diff --git a/.devcontainer/application/setup.php b/.devcontainer/application/setup.php
--- a/.devcontainer/application/setup.php
+++ b/.devcontainer/application/setup.php
@@ -3,8 +3,8 @@
 
 require_once './scripts/__init_script__.php';
 
-const query = new PhabricatorAuthProviderConfigQuery();
-$config = query
+$query = new PhabricatorAuthProviderConfigQuery();
+$config = $query
     ->setViewer(PhabricatorUser::getOmnipotentUser())
     ->withIDs(array(1))
     ->executeOne();
@@ -14,6 +14,9 @@
     return;
 }
 
+echo phutil_console_wrap(
+  "Setting up dev environment with user admin/hunter2\n");
+
 $password1 = new PhutilOpaqueEnvelope('hunter2');
 
 $config = id(new PhabricatorAuthProviderConfig())
diff --git a/.devcontainer/application/startup.sh b/.devcontainer/application/startup.sh
--- a/.devcontainer/application/startup.sh
+++ b/.devcontainer/application/startup.sh
@@ -38,13 +38,6 @@
 
 echo '["/srv/phorge/deepclone/src"]' | ./bin/config set load-libraries --stdin
 
-php setup.php
-
-if [ -e /user-config/script.post ]; then
-    echo "Applying post-configuration script..."
-    /user-config/script.post
-fi
-
 popd
 
 pushd /srv/phorge/phorge/support/aphlict/server
@@ -53,7 +46,10 @@
 
 popd
 
-find /srv/phorge/deepclone
-
 cp /etc/nginx/nginx.conf.org /etc/nginx/nginx.conf
-/srv/phorge/phorge/bin/storage upgrade --force
+
+pushd /srv/phorge/phorge
+
+./bin/storage upgrade --force
+
+php setup.php
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
@@ -10,13 +10,19 @@
   '__library_version__' => 2,
   'class' => array(
     'DeepcloneApplication' => 'deepclone/application/DeepcloneApplication.php',
+    'DeepcloneBaseTransaction' => 'xaction/DeepcloneBaseTransaction.php',
     'DeepcloneController' => 'deepclone/controller/DeepcloneController.php',
+    'DeepcloneSourceTransaction' => 'xaction/DeepcloneSourceTransaction.php',
+    'DeepcloneTargetTransaction' => 'xaction/DeepcloneTargetTransaction.php',
     'DeepcloneUIEventListener' => 'deepclone/events/DeepcloneUIEventListener.php',
   ),
   'function' => array(),
   'xmap' => array(
     'DeepcloneApplication' => 'PhabricatorApplication',
+    'DeepcloneBaseTransaction' => 'ManiphestTaskTransactionType',
     'DeepcloneController' => 'ManiphestController',
+    'DeepcloneSourceTransaction' => 'DeepcloneBaseTransaction',
+    'DeepcloneTargetTransaction' => 'ManiphestTaskTransactionType',
     'DeepcloneUIEventListener' => 'PhabricatorEventListener',
   ),
 ));
diff --git a/src/deepclone/controller/DeepcloneController.php b/src/deepclone/controller/DeepcloneController.php
--- a/src/deepclone/controller/DeepcloneController.php
+++ b/src/deepclone/controller/DeepcloneController.php
@@ -12,100 +12,17 @@
       return new Aphront404Response();
     }
 
-    $task = id(new ManiphestTaskQuery())
-      ->setViewer($viewer)
-      ->withPHIDs(array($task_id))
-      ->requireCapabilities(
-        array(
-          PhabricatorPolicyCapability::CAN_VIEW,
-          PhabricatorPolicyCapability::CAN_EDIT,
-        )
-      )
-      ->needSubscriberPHIDs(true)
-      ->executeOne();
+    $task = $this->getTask($task_id);
 
     if (!$task) {
       throw new Exception("Task ID: $task_id");
       return new Aphront404Response();
     }
 
+    $transactions = $this->getTransactions($task);
+
     if ($request->isFormPost()) {
-      return $this->triggerTransaction($request, $task);
-      // $properties = $provider->readFormValuesFromRequest($request);
-      // list($errors, $issues, $properties) = $provider->processEditForm(
-      //   $request,
-      //   $properties);
-
-      // $xactions = array();
-
-      // if (!$errors) {
-      //   if ($is_new) {
-      //     if (!phutil_nonempty_string($config->getProviderType())) {
-      //       $config->setProviderType($provider->getProviderType());
-      //     }
-      //     if (!phutil_nonempty_string($config->getProviderDomain())) {
-      //       $config->setProviderDomain($provider->getProviderDomain());
-      //     }
-      //   }
-
-      //   $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //     ->setTransactionType(
-      //       PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN)
-      //     ->setNewValue($request->getInt('allowLogin', 0));
-
-      //   $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //     ->setTransactionType(
-      //       PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION)
-      //     ->setNewValue($request->getInt('allowRegistration', 0));
-
-      //   $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //     ->setTransactionType(
-      //       PhabricatorAuthProviderConfigTransaction::TYPE_LINK)
-      //     ->setNewValue($request->getInt('allowLink', 0));
-
-      //   $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //     ->setTransactionType(
-      //       PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK)
-      //     ->setNewValue($request->getInt('allowUnlink', 0));
-
-      //   $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //     ->setTransactionType(
-      //       PhabricatorAuthProviderConfigTransaction::TYPE_TRUST_EMAILS)
-      //     ->setNewValue($request->getInt('trustEmails', 0));
-
-      //   if ($provider->supportsAutoLogin()) {
-      //     $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //       ->setTransactionType(
-      //         PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN)
-      //       ->setNewValue($request->getInt('autoLogin', 0));
-      //   }
-
-      //   foreach ($properties as $key => $value) {
-      //     $xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
-      //       ->setTransactionType(
-      //         PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY)
-      //       ->setMetadataValue('auth:property', $key)
-      //       ->setNewValue($value);
-      //   }
-
-      //   if ($is_new) {
-      //     $config->save();
-      //   }
-
-      //   $editor = id(new PhabricatorAuthProviderConfigEditor())
-      //     ->setActor($viewer)
-      //     ->setContentSourceFromRequest($request)
-      //     ->setContinueOnNoEffect(true);
-
-      //   try {
-      //     $editor->applyTransactions($config, $xactions);
-      //     $next_uri = $config->getURI();
-
-      //     return id(new AphrontRedirectResponse())->setURI($next_uri);
-      //   } catch (Exception $ex) {
-      //     $validation_exception = $ex;
-      //   }
-      // }
+      return $this->handleClone($request, $task, $transactions);
     } else {
       return $this->buildForm($task);
     }
@@ -115,24 +32,13 @@
   {
     $form = id(new AphrontFormView())
       ->setUser($this->getViewer())
-      ->appendChild(
-        id(new AphrontFormCheckboxControl())
-          ->setLabel(pht('Copy Comments'))
-          ->addCheckbox(
-            'copy_comments',
-            1,
-            pht('Copy all the comments of the task/s to the new task/s'),
-          )
-      )
       ->appendChild(
         id(new AphrontFormCheckboxControl())
           ->setLabel('Deep Clone Task')
           ->addCheckbox(
             'deep_clone',
             1,
-            'Create a clone of all the sub tasks of this task',
-          )
-      );
+            'Create a clone of all the sub tasks of this task'));
 
     $handle = $this->getHandle($task);
 
@@ -147,8 +53,6 @@
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText($this->generateTitle($task))
-      // ->setFormErrors($errors)
-      // ->setValidationException($validation_exception)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
@@ -166,7 +70,7 @@
   {
     $handle = $this->getHandle($task);
 
-    return pht('Deep clone task "%s" (%s)', $task->getTitle(), $handle->getName());
+    return pht('Clone task "%s" (%s)', $task->getTitle(), $handle->getName());
   }
 
   private function getHandle(ManiphestTask $task)
@@ -178,12 +82,155 @@
     return $handle;
   }
 
-  private function triggerTransaction(AphrontRequest $request, ManiphestTask $task) {
-    $deepCloneRaw = $request->getStr('deep_clone');
-    $deepClone = $deepCloneRaw === '1';
-    $copyCommentsRaw = $request->getStr('copy_comments');
-    $copyComments = $copyCommentsRaw === '1';
+  private function getTask($task_id) {
+    $query = new ManiphestTaskQuery();
+
+    $task = $query
+      ->setViewer($this->getViewer())
+      ->withPHIDs(array($task_id))
+      ->requireCapabilities(
+        array(
+          PhabricatorPolicyCapability::CAN_VIEW,
+          PhabricatorPolicyCapability::CAN_EDIT,
+        ))
+      ->needSubscriberPHIDs(true)
+      ->executeOne();
+
+      $field_list = PhabricatorCustomField::getObjectFields(
+        $task,
+        PhabricatorCustomField::ROLE_VIEW);
+      $field_list
+        ->setViewer($this->getViewer())
+        ->readFieldsFromStorage($task);
+
+    return $task;
+  }
+
+  private function getTransactions(ManiphestTask $task) {
+    $transactions = id(new ManiphestTransactionQuery())
+      ->setViewer($this->getViewer())
+      ->withObjectPHIDs(mpull([$task], 'getPHID'))
+      ->needComments(true)
+      ->execute();
+
+    return $transactions;
+  }
+
+  private function handleClone(
+    AphrontRequest $request,
+    ManiphestTask $task,
+    array $transactions) {
+    $deep_clone = $request->getStr('deep_clone') === '1';
+
+    $transaction_editor = id(new ManiphestTransactionEditor())
+      ->setActor($this->getViewer())
+      ->setContentSourceFromRequest($request)
+      ->setContinueOnNoEffect(true);
+
+    $task_clone = $this->cloneTask(
+      $task,
+      $transactions,
+      $deep_clone,
+      $transaction_editor);
+
+    return id(new AphrontRedirectResponse())->setURI($task_clone->getURI());
+  }
+
+
+  private function cloneTask(
+    ManiphestTask $task,
+    array $transactions,
+    $deep_clone,
+    $transaction_editor,
+    $parent = null) {
+    $task_clone = new ManiphestTask();
+
+    $task_clone->setTitle($task->getTitle());
+    $task_clone->setDescription($task->getDescription());
+    $task_clone->setAuthorPHID($task->getAuthorPHID());
+    $task_clone->setOwnerPHID($task->getOwnerPHID());
+    $task_clone->setStatus($task->getStatus());
+    $task_clone->setPriority($task->getPriority());
+    $task_clone->setSubtype($task->getSubtype());
+    $task_clone->setViewPolicy($task->getViewPolicy());
+    $task_clone->setEditPolicy($task->getEditPolicy());
+
+    $xactions = [];
+
+    $xactions[] = id(new ManiphestTransaction())
+      ->setTransactionType(PhabricatorCoreCreateTransaction::TRANSACTIONTYPE);
+
+    $xactions[] = id(new ManiphestTransaction())
+      ->setTransactionType(DeepcloneTargetTransaction::TRANSACTIONTYPE)
+      ->setNewValue($task->getPHID());
+
+
+    foreach ($transactions as &$transaction) {
+      if ($transaction->getTransactionType() !== 'core:customfield') {
+        continue;
+      }
+
+      $transaction_clone = id(new ManiphestTransaction())
+        ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD);
+
+      $transaction_clone->setMetadataValue(
+        'customfield:key',
+        $transaction->getMetadataValue('customfield:key'));
+      $transaction_clone->setOldValue(null);
+      $transaction_clone->setNewValue($transaction->getNewValue());
+
+      $xactions[] = $transaction_clone;
+    }
+
+    if ($parent !== null) {
+      $xactions[] = id(new ManiphestTransaction())
+        ->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE)
+        ->setNewValue($parent->getPHID());
+    }
+
+    $transaction_editor->applyTransactions($task_clone, $xactions);
+
+    $xactions = [];
+
+    $xactions[] = id(new ManiphestTransaction())
+      ->setTransactionType(DeepcloneSourceTransaction::TRANSACTIONTYPE)
+      ->setNewValue($task_clone->getPHID());
+
+    $transaction_editor->applyTransactions($task, $xactions);
+
+    $task_clone->save();
+
+    if ($deep_clone === false) {
+      return $task_clone;
+    }
+
+    $task_graph = id(new ManiphestTaskGraph())
+      ->setViewer($this->getViewer())
+      ->setSeedPHID($task->getPHID())
+      ->setLimit(200)
+      ->loadGraph();
+
+    if ($task_graph->isEmpty()) {
+      return $task_clone;
+    }
+
+    $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
+    $subtask_map = $task_graph->getEdges($subtask_type);
+
+    $subtask_list = idx($subtask_map, $task->getPHID(), array());
+
+    foreach ($subtask_list as &$subtask_phid) {
+      $sub_task = $this->getTask($subtask_phid);
+      $sub_transactions = $this->getTransactions($task);
+
+      $this->cloneTask(
+        $sub_task,
+        $sub_transactions,
+        $deep_clone,
+        $transaction_editor,
+        $task_clone);
+    }
 
-    throw new Exception("Deep Clone $deepClone $deepCloneRaw Copy Comments $copyComments $copyCommentsRaw");
+    return $task_clone;
   }
 }
diff --git a/src/xaction/DeepcloneBaseTransaction.php b/src/xaction/DeepcloneBaseTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/xaction/DeepcloneBaseTransaction.php
@@ -0,0 +1,24 @@
+<?php
+
+abstract class DeepcloneBaseTransaction
+  extends ManiphestTaskTransactionType {
+  public function generateOldValue($object) {
+    return 'donthide';
+  }
+
+  public function getActionName() {
+    return pht('Cloned');
+  }
+
+  public function getIcon() {
+    return 'fa-clone';
+  }
+
+  public function shouldHideForFeed() {
+    return false;
+  }
+
+  public function shouldHide() {
+    return false;
+  }
+}
diff --git a/src/xaction/DeepcloneSourceTransaction.php b/src/xaction/DeepcloneSourceTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/xaction/DeepcloneSourceTransaction.php
@@ -0,0 +1,21 @@
+<?php
+
+final class DeepcloneSourceTransaction
+  extends DeepcloneBaseTransaction {
+  const TRANSACTIONTYPE = 'clone_source';
+
+  public function getTitle() {
+    $source = $this->getNewValue();
+    $target = $this->getObject();
+
+    return pht(
+      '%s cloned %s to %s',
+      $this->renderAuthor(),
+      $this->renderHandle($target->getPHID()),
+      $this->renderHandle($source));
+  }
+
+  public function getTitleForFeed() {
+    return $this->getTitle();
+  }
+}
diff --git a/src/xaction/DeepcloneTargetTransaction.php b/src/xaction/DeepcloneTargetTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/xaction/DeepcloneTargetTransaction.php
@@ -0,0 +1,21 @@
+<?php
+
+final class DeepcloneTargetTransaction
+  extends DeepcloneBaseTransaction {
+  const TRANSACTIONTYPE = 'clone_target';
+
+  public function getTitle() {
+    $source = $this->getNewValue();
+    $target = $this->getObject();
+
+    return pht(
+      '%s cloned %s to %s',
+      $this->renderAuthor(),
+      $this->renderHandle($source),
+      $this->renderHandle($target->getPHID()));
+  }
+
+  public function getTitleForFeed() {
+    return $this->getTitle();
+  }
+}