@@ -1822,3 +1822,114 @@ func TestTriggerProcessing(t *testing.T) {
18221822 }
18231823 })
18241824}
1825+
1826+ // TestUpdateTaskDoesNotResetStatus verifies that UpdateTask() calls in
1827+ // setupWorktree/setupSharedWorkDir don't accidentally reset the task status
1828+ // back to "queued" after executeTask has set it to "processing".
1829+ // This was the root cause of the daemon terminating Claude immediately
1830+ // for project-based (non-worktree) tasks.
1831+ func TestUpdateTaskDoesNotResetStatus (t * testing.T ) {
1832+ tmpDir := t .TempDir ()
1833+ dbPath := filepath .Join (tmpDir , "test.db" )
1834+ database , err := db .Open (dbPath )
1835+ if err != nil {
1836+ t .Fatalf ("failed to open database: %v" , err )
1837+ }
1838+ defer database .Close ()
1839+
1840+ // Create a project
1841+ if err := database .CreateProject (& db.Project {Name : "testproj" , Path : tmpDir }); err != nil {
1842+ t .Fatal (err )
1843+ }
1844+
1845+ // Create a task in queued status (as processNextTask would see it)
1846+ task := & db.Task {
1847+ Title : "Say hello" ,
1848+ Status : db .StatusQueued ,
1849+ Project : "testproj" ,
1850+ }
1851+ if err := database .CreateTask (task ); err != nil {
1852+ t .Fatal (err )
1853+ }
1854+
1855+ // Simulate what executeTask does:
1856+ // 1. Update DB status to processing
1857+ if err := database .UpdateTaskStatus (task .ID , db .StatusProcessing ); err != nil {
1858+ t .Fatal (err )
1859+ }
1860+ // 2. Keep the struct in sync (this is the fix)
1861+ task .Status = db .StatusProcessing
1862+
1863+ // 3. Simulate setupSharedWorkDir which calls UpdateTask
1864+ task .WorktreePath = tmpDir
1865+ task .BranchName = ""
1866+ if err := database .UpdateTask (task ); err != nil {
1867+ t .Fatal (err )
1868+ }
1869+
1870+ // Verify the status is still "processing" (not reset to "queued")
1871+ updatedTask , err := database .GetTask (task .ID )
1872+ if err != nil {
1873+ t .Fatal (err )
1874+ }
1875+ if updatedTask .Status != db .StatusProcessing {
1876+ t .Errorf ("expected status %q after UpdateTask, got %q (status was reset!)" ,
1877+ db .StatusProcessing , updatedTask .Status )
1878+ }
1879+ }
1880+
1881+ // TestCleanupWorktreeNonWorktreeTask verifies that CleanupWorktree handles
1882+ // non-worktree tasks correctly (where WorktreePath is the project root).
1883+ // It should NOT attempt "git worktree remove" on the main working tree.
1884+ func TestCleanupWorktreeNonWorktreeTask (t * testing.T ) {
1885+ tmpDir := t .TempDir ()
1886+ dbPath := filepath .Join (tmpDir , "test.db" )
1887+ database , err := db .Open (dbPath )
1888+ if err != nil {
1889+ t .Fatalf ("failed to open database: %v" , err )
1890+ }
1891+ defer database .Close ()
1892+
1893+ projectDir := filepath .Join (tmpDir , "myproject" )
1894+ os .MkdirAll (projectDir , 0755 )
1895+
1896+ // Create project
1897+ if err := database .CreateProject (& db.Project {Name : "testproj" , Path : projectDir }); err != nil {
1898+ t .Fatal (err )
1899+ }
1900+
1901+ cfg := config .New (database )
1902+ exec := New (database , cfg )
1903+
1904+ // Non-worktree task: WorktreePath == projectDir
1905+ task := & db.Task {
1906+ Title : "Test task" ,
1907+ Status : db .StatusBlocked ,
1908+ Project : "testproj" ,
1909+ WorktreePath : projectDir ,
1910+ }
1911+ if err := database .CreateTask (task ); err != nil {
1912+ t .Fatal (err )
1913+ }
1914+
1915+ // Write files that CleanupWorktree should remove
1916+ os .MkdirAll (filepath .Join (projectDir , ".claude" ), 0755 )
1917+ os .WriteFile (filepath .Join (projectDir , ".envrc" ), []byte ("export WORKTREE_TASK_ID=1" ), 0644 )
1918+ os .WriteFile (filepath .Join (projectDir , ".claude" , "settings.local.json" ), []byte ("{}" ), 0644 )
1919+
1920+ // CleanupWorktree should NOT error (no git worktree remove on main tree)
1921+ err = exec .CleanupWorktree (task )
1922+ if err != nil {
1923+ t .Errorf ("CleanupWorktree should not error for non-worktree tasks, got: %v" , err )
1924+ }
1925+
1926+ // Verify .envrc was cleaned up
1927+ if _ , err := os .Stat (filepath .Join (projectDir , ".envrc" )); ! os .IsNotExist (err ) {
1928+ t .Error ("expected .envrc to be removed" )
1929+ }
1930+
1931+ // Verify settings.local.json was cleaned up
1932+ if _ , err := os .Stat (filepath .Join (projectDir , ".claude" , "settings.local.json" )); ! os .IsNotExist (err ) {
1933+ t .Error ("expected settings.local.json to be removed" )
1934+ }
1935+ }
0 commit comments