@masslessai/push-todo 3.8.2 → 3.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/daemon.js +54 -0
  2. package/package.json +1 -1
package/lib/daemon.js CHANGED
@@ -1775,6 +1775,53 @@ async function pollAndExecute() {
1775
1775
  updateStatusFile();
1776
1776
  }
1777
1777
 
1778
+ /**
1779
+ * Recover tasks orphaned by a previous daemon instance.
1780
+ * When the daemon restarts, tasks claimed by this machine may be stuck in 'running'
1781
+ * with no process working on them. Reset them to 'queued' so the normal poll cycle
1782
+ * picks them up and autoHeal detects prior work.
1783
+ */
1784
+ async function recoverOrphanedTasks() {
1785
+ const suffix = getWorktreeSuffix();
1786
+ if (!suffix) return;
1787
+
1788
+ try {
1789
+ const params = new URLSearchParams();
1790
+ params.set('execution_status', 'running');
1791
+
1792
+ const response = await apiRequest(`synced-todos?${params}`);
1793
+ if (!response.ok) return;
1794
+
1795
+ const data = await response.json();
1796
+ const tasks = data.todos || [];
1797
+
1798
+ // Filter to tasks owned by THIS machine (branch contains our suffix)
1799
+ const orphaned = tasks.filter(t => {
1800
+ const branch = t.executionBranch || t.execution_branch || '';
1801
+ return branch.endsWith(suffix);
1802
+ });
1803
+
1804
+ if (orphaned.length === 0) return;
1805
+
1806
+ log(`Recovering ${orphaned.length} orphaned task(s) from previous daemon instance`);
1807
+
1808
+ for (const task of orphaned) {
1809
+ const dn = task.displayNumber || task.display_number;
1810
+ log(`Task #${dn}: resetting from 'running' to 'queued' (orphaned by restart)`);
1811
+ await updateTaskStatus(dn, 'queued', {
1812
+ event: {
1813
+ type: 'requeued',
1814
+ timestamp: new Date().toISOString(),
1815
+ machineName: getMachineName() || undefined,
1816
+ summary: 'Daemon restarted — re-queuing for auto-heal'
1817
+ }
1818
+ });
1819
+ }
1820
+ } catch (error) {
1821
+ log(`Orphaned task recovery failed (non-fatal): ${error.message}`);
1822
+ }
1823
+ }
1824
+
1778
1825
  async function mainLoop() {
1779
1826
  daemonStartTime = new Date().toISOString();
1780
1827
 
@@ -1815,6 +1862,13 @@ async function mainLoop() {
1815
1862
  writeFileSync(VERSION_FILE, getVersion());
1816
1863
  } catch {}
1817
1864
 
1865
+ // Recover orphaned tasks from previous daemon instance
1866
+ // When the daemon restarts (self-update, crash, reboot), tasks may be stuck
1867
+ // in 'running' with no process actually working on them. Reset them to 'queued'
1868
+ // so the normal poll cycle picks them up and autoHeal handles the rest.
1869
+ // See: docs/20260211_auto_complete_failure_investigation.md
1870
+ await recoverOrphanedTasks();
1871
+
1818
1872
  // Initial status
1819
1873
  updateStatusFile();
1820
1874
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "3.8.2",
3
+ "version": "3.8.3",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {