Skip running cleanup script if no changes (vibe-kanban) (#523)

* ## Implementation Complete 

I've successfully implemented the feature to skip running cleanup scripts when no changes are made during coding agent execution.

### **Changes Made**

1. **Modified [`GitService::commit()`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/services/src/services/git.rs#L174-L218)**:
   - Changed return type from `Result<(), GitServiceError>` to `Result<bool, GitServiceError>`
   - Returns `Ok(false)` when no changes to commit
   - Returns `Ok(true)` when changes are committed

2. **Updated [`Container` trait](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/services/src/services/container.rs#L125)**:
   - Changed `try_commit_changes()` signature to return `Result<bool, ContainerError>`

3. **Updated [`LocalContainerService`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/local-deployment/src/container.rs#L889-L954)**:
   - Modified implementation to propagate commit feedback
   - Returns `false` for non-commit operations, `true`/`false` based on git changes

4. **Enhanced [execution monitor logic](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/local-deployment/src/container.rs#L335-L374)**:
   - Conditionally calls `try_start_next_action()` based on whether changes were committed
   - Skips cleanup script when coding agent made no changes
   - Preserves existing behavior for setup scripts (always proceed to coding agent)
   - Added informative logging when cleanup is skipped

### **Behavior**

**Before:**
```
CodingAgent (no changes) → try_commit_changes() → CleanupScript (always runs)
```

**After:**
```
CodingAgent (no changes) → try_commit_changes() returns false → Skip CleanupScript 
CodingAgent (has changes) → try_commit_changes() returns true → CleanupScript runs 
```

### **Safety Features**
- **Conservative error handling**: Commit failures are treated as "changes made" to ensure cleanup runs
- **Preserved workflows**: Setup scripts always proceed to coding agent (unchanged)
- **Informative logging**: Clear indication when cleanup is skipped
- **Type safety**: Compiler enforces correct return types throughout the call chain

### **Verification**
-  Code compiles successfully 
-  All existing tests pass (47/47)
-  Type checker passes
-  No breaking changes to other parts of the codebase

The implementation is complete and ready for use!

* Cleanup script changes for task attempt 92b80e80-dbc1-470c-a3c8-172c38549b61

* Cleanup script changes for task attempt 92b80e80-dbc1-470c-a3c8-172c38549b61

* ## Task Status Fix Complete 

I've successfully fixed the issue where tasks weren't transitioning from `InProgress` to `InReview` when cleanup scripts are skipped.

### **Changes Made**

1. **Created [`finalize_task()` helper method](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/local-deployment/src/container.rs#L116-L128)**:
   ```rust
   async fn finalize_task(
       db: &DBService,
       config: &Arc<RwLock<Config>>, 
       ctx: &ExecutionContext,
   ) {
       // Updates task status to InReview
       // Sends notifications via NotificationService
   }
   ```

2. **Added manual finalization when skipping cleanup** ([lines 389-391](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/local-deployment/src/container.rs#L389-L391)):
   ```rust
   } else {
       tracing::info!("Skipping cleanup script - no changes made by coding agent");
       // Manually finalize task since we're bypassing normal execution flow
       Self::finalize_task(&db, &config, &ctx).await;
   }
   ```

3. **Refactored existing finalization** to use the helper method ([line 395](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/crates/local-deployment/src/container.rs#L395-L397)):
   ```rust
   if Self::should_finalize(&ctx) {
       Self::finalize_task(&db, &config, &ctx).await;
   }
   ```

### **Fixed Flow**

**Before (Broken):**
```
CodingAgent (no changes) → Skip cleanup → should_finalize() = false → Task stuck in InProgress 
```

**After (Fixed):**
```
CodingAgent (no changes) → Skip cleanup → Manual finalize_task() → Task → InReview 
CodingAgent (has changes) → Cleanup runs → should_finalize() = true → Task → InReview 
```

### **Benefits**
-  **Fixes the bug**: Tasks now properly transition to `InReview` when cleanup is skipped
-  **DRY principle**: Reuses finalization logic instead of duplicating 
-  **No double finalization**: Helper is idempotent and safely reusable
-  **Preserves notifications**: Same notification behavior as normal cleanup completion
-  **Clean separation**: `should_finalize()` logic unchanged for normal flows

### **Verification**
-  Code compiles successfully
-  All tests pass (47/47) 
-  Type checker passes
-  Same finalization behavior for both paths

The complete feature is now implemented: cleanup scripts are skipped when no changes are made during coding agent execution, AND tasks properly transition to `InReview` status in both scenarios!

* Cleanup script changes for task attempt 92b80e80-dbc1-470c-a3c8-172c38549b61

* ## UI Copy Updates Complete 

I've successfully updated the project settings UI to clearly communicate that the cleanup script only runs when changes are made by the coding agent.

### **Changes Made**

1. **Updated description text** in [`project-form-fields.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/frontend/src/components/projects/project-form-fields.tsx#L249-L253):
   ```diff
   - This script will run after coding agent execution is complete.
   + This script runs after coding agent execution only if changes were made.
   + Use it for quality assurance tasks like running linters, formatters, tests, 
   + or other validation steps. If no changes are made, this script is skipped.
   ```

2. **Updated Unix placeholder** in [`script-placeholders.ts`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/frontend/src/utils/script-placeholders.ts#L36-L38):
   ```diff
   - # This runs after coding agent execution
   + # This runs after coding agent execution - only if changes were made
   ```

3. **Updated Windows placeholder** in [`script-placeholders.ts`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-92b8-skip-runni/frontend/src/utils/script-placeholders.ts#L20-L22):
   ```diff
   - REM This runs after coding agent execution
   + REM This runs after coding agent execution - only if changes were made
   ```

### **User Experience**

Now when users configure a cleanup script in project settings, they will see:

**Label**: "Cleanup Script (Optional)"

**Description**: 
> This script runs after coding agent execution **only if changes were made**. Use it for quality assurance tasks like running linters, formatters, tests, or other validation steps. If no changes are made, this script is skipped.

**Placeholder text**:
```bash
#!/bin/bash
# Add cleanup commands here...
# This runs after coding agent execution - only if changes were made
```

### **Benefits**
-  **Clear expectations**: Users understand the conditional behavior upfront
-  **Emphasized condition**: "only if changes were made" is bolded for visibility  
-  **Comprehensive coverage**: Updated both description and placeholder text
-  **Cross-platform**: Updated both Unix and Windows placeholders
-  **TypeScript verified**: Frontend code compiles successfully

Users will now have clear visibility into when their cleanup scripts will and won't run, preventing any confusion about the new conditional execution behavior!

* Cleanup script changes for task attempt 92b80e80-dbc1-470c-a3c8-172c38549b61
This commit is contained in:
Louis Knight-Webb
2025-08-19 18:42:40 +01:00
committed by GitHub
parent 924ff29644
commit 1caa5ce1d8
6 changed files with 71 additions and 36 deletions

View File

@@ -69,6 +69,14 @@ impl Default for EntryIndexProvider {
}
}
#[cfg(test)]
impl EntryIndexProvider {
/// Test-only constructor for a fresh provider starting at 0
pub fn test_new() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -103,11 +111,3 @@ mod tests {
assert_eq!(provider.current(), 2);
}
}
#[cfg(test)]
impl EntryIndexProvider {
/// Test-only constructor for a fresh provider starting at 0
pub fn test_new() -> Self {
Self::new()
}
}

View File

@@ -114,6 +114,15 @@ impl LocalContainerService {
))
}
/// Finalize task execution by updating status to InReview and sending notifications
async fn finalize_task(db: &DBService, config: &Arc<RwLock<Config>>, ctx: &ExecutionContext) {
if let Err(e) = Task::update_status(&db.pool, ctx.task.id, TaskStatus::InReview).await {
tracing::error!("Failed to update task status to InReview: {e}");
}
let notify_cfg = config.read().await.notifications.clone();
NotificationService::notify_execution_halted(notify_cfg, ctx).await;
}
/// Defensively check for externally deleted worktrees and mark them as deleted in the database
async fn check_externally_deleted_worktrees(db: &DBService) -> Result<(), DeploymentError> {
let active_attempts = TaskAttempt::find_by_worktree_deleted(&db.pool).await?;
@@ -335,28 +344,52 @@ impl LocalContainerService {
ExecutionProcessStatus::Completed
) && exit_code == Some(0)
{
if let Err(e) = container.try_commit_changes(&ctx).await {
tracing::error!("Failed to commit changes after execution: {}", e);
}
// Commit changes (if any) and get feedback about whether changes were made
let changes_committed = match container.try_commit_changes(&ctx).await {
Ok(committed) => committed,
Err(e) => {
tracing::error!(
"Failed to commit changes after execution: {}",
e
);
// Treat commit failures as if changes were made to be safe
true
}
};
// If the process exited successfully, start the next action
if let Err(e) = container.try_start_next_action(&ctx).await {
tracing::error!(
"Failed to start next action after completion: {}",
e
// Determine whether to start the next action based on execution context
let should_start_next = if matches!(
ctx.execution_process.run_reason,
ExecutionProcessRunReason::CodingAgent
) {
// Skip CleanupScript when CodingAgent produced no changes
changes_committed
} else {
// SetupScript always proceeds to CodingAgent
true
};
if should_start_next {
// If the process exited successfully, start the next action
if let Err(e) = container.try_start_next_action(&ctx).await {
tracing::error!(
"Failed to start next action after completion: {}",
e
);
}
} else {
tracing::info!(
"Skipping cleanup script for task attempt {} - no changes made by coding agent",
ctx.task_attempt.id
);
// Manually finalize task since we're bypassing normal execution flow
Self::finalize_task(&db, &config, &ctx).await;
}
}
if Self::should_finalize(&ctx) {
if let Err(e) =
Task::update_status(&db.pool, ctx.task.id, TaskStatus::InReview)
.await
{
tracing::error!("Failed to update task status to InReview: {e}");
}
let notify_cfg = config.read().await.notifications.clone();
NotificationService::notify_execution_halted(notify_cfg, &ctx).await;
Self::finalize_task(&db, &config, &ctx).await;
}
// Fire event when CodingAgent execution has finished
@@ -886,12 +919,12 @@ impl ContainerService for LocalContainerService {
.await
}
async fn try_commit_changes(&self, ctx: &ExecutionContext) -> Result<(), ContainerError> {
async fn try_commit_changes(&self, ctx: &ExecutionContext) -> Result<bool, ContainerError> {
if !matches!(
ctx.execution_process.run_reason,
ExecutionProcessRunReason::CodingAgent | ExecutionProcessRunReason::CleanupScript,
) {
return Ok(());
return Ok(false);
}
let message = match ctx.execution_process.run_reason {
@@ -950,7 +983,8 @@ impl ContainerService for LocalContainerService {
message
);
Ok(self.git().commit(Path::new(container_ref), &message)?)
let changes_committed = self.git().commit(Path::new(container_ref), &message)?;
Ok(changes_committed)
}
/// Copy files from the original project directory to the worktree

View File

@@ -122,7 +122,7 @@ pub trait ContainerService {
execution_process: &ExecutionProcess,
) -> Result<(), ContainerError>;
async fn try_commit_changes(&self, ctx: &ExecutionContext) -> Result<(), ContainerError>;
async fn try_commit_changes(&self, ctx: &ExecutionContext) -> Result<bool, ContainerError>;
async fn copy_project_files(
&self,

View File

@@ -171,7 +171,7 @@ impl GitService {
Ok(())
}
pub fn commit(&self, path: &Path, message: &str) -> Result<(), GitServiceError> {
pub fn commit(&self, path: &Path, message: &str) -> Result<bool, GitServiceError> {
let repo = Repository::open(path)?;
// Check if there are any changes to commit
@@ -189,7 +189,7 @@ impl GitService {
if !has_changes {
tracing::debug!("No changes to commit!");
return Ok(());
return Ok(false);
}
// Get the current HEAD commit
@@ -214,7 +214,7 @@ impl GitService {
&[&parent_commit],
)?;
Ok(())
Ok(true)
}
/// Get diffs between branches or worktree changes

View File

@@ -247,9 +247,10 @@ export function ProjectFormFields({
className="w-full px-3 py-2 border border-input bg-background text-foreground rounded-md resize-vertical focus:outline-none focus:ring-2 focus:ring-ring"
/>
<p className="text-sm text-muted-foreground">
This script will run after coding agent execution is complete. Use it
for quality assurance tasks like running linters, formatters, tests,
or other validation steps.
This script runs after coding agent execution{' '}
<strong>only if changes were made</strong>. Use it for quality
assurance tasks like running linters, formatters, tests, or other
validation steps. If no changes are made, this script is skipped.
</p>
</div>

View File

@@ -19,7 +19,7 @@ npm run dev
REM Add dev server start command here...`,
cleanup: `@echo off
REM Add cleanup commands here...
REM This runs after coding agent execution`,
REM This runs after coding agent execution - only if changes were made`,
};
}
}
@@ -35,7 +35,7 @@ npm run dev
# Add dev server start command here...`,
cleanup: `#!/bin/bash
# Add cleanup commands here...
# This runs after coding agent execution`,
# This runs after coding agent execution - only if changes were made`,
};
}
}