In Chapter 1: Tool Definition & Interface, we built the basic structure of the ExitWorktreeTool. We created a switch that lets an AI agent deciding between keeping a workspace or deleting it.
However, we ended with a cliffhanger: What happens if the AI tries to delete a workspace that contains unsaved code?
In this chapter, we will build the "Safety Gate." This is the logic that screams, "Wait! You haven't saved your work!" before allowing a deletion to happen.
Imagine you are writing an essay in a word processor. You type for three hours, then accidentally click the "Close" button.
Our AI tool needs to be "Good Software." Since ExitWorktreeTool has the power to delete files (using action: "remove"), we must ensure it doesn't delete valuable, uncommitted work by accident.
User: "I'm done with this task. Clean up."
AI: Checks the folder. "Wait, I wrote a file called fix.ts but I forgot to commit it to Git. I shouldn't delete this yet."
Goal:
To know if files have been changed, we ask Git. Specifically, we use the command git status.
If you run git status in a terminal, it gives you a human-readable message. For our tool, we use git status --porcelain. This gives us a simple, machine-readable list of changed files.
Example Code:
// Helper: Check for uncommitted files
const status = await execFileNoThrow('git', [
'-C', worktreePath,
'status',
'--porcelain' // Output is simple text, one line per file
])
// If the output isn't empty, we have "dirty" files
const changedFilesCount = count(
status.stdout.split('\n'),
line => line.trim() !== ''
)
Explanation: We run the command. If changedFilesCount is greater than 0, the user has "unsaved changes."
Sometimes the AI has committed the files, but those commits are stuck in the temporary branch. If we delete the branch now, those commits are lost.
We compare the current state (HEAD) against where we started (originalHeadCommit).
Example Code:
// Helper: Check for new commits since we started
const revList = await execFileNoThrow('git', [
'-C', worktreePath,
'rev-list',
'--count',
`${originalHeadCommit}..HEAD` // Count commits between Start and Now
])
const newCommitsCount = parseInt(revList.stdout.trim(), 10) || 0
Explanation: If newCommitsCount is greater than 0, it means the AI made progress but hasn't pushed or merged it yet.
Here is how the tool decides whether to allow the remove action.
validateInput Method
In the Tool definition, we use the validateInput function to act as the gatekeeper. This runs before the tool actually executes any deletion.
We will look at the implementation in ExitWorktreeTool.ts.
First, we ensure we are actually inside a worktree session. We can't exit a session that doesn't exist.
// ExitWorktreeTool.ts
async validateInput(input) {
const session = getCurrentWorktreeSession()
if (!session) {
return {
result: false,
message: 'No active worktree session.'
}
}
// ... continued below
This is the core logic. If the user wants to remove the worktree, but has not explicitly said discard_changes: true, we must run our checks.
// ... inside validateInput
if (input.action === 'remove' && !input.discard_changes) {
// Use our helper to count files and commits
const summary = await countWorktreeChanges(
session.worktreePath,
session.originalHeadCommit
)
// ... continued below
Explanation: We only check if discard_changes is false. If it's true, the AI is effectively saying "I know what I'm doing, delete it anyway."
If our helper returns a number greater than zero, we block the tool execution.
// ... inside validateInput
const { changedFiles, commits } = summary
if (changedFiles > 0 || commits > 0) {
return {
result: false, // STOP! Do not proceed.
message: `Worktree has ${changedFiles} files and ${commits} commits. Refusing to remove.`,
errorCode: 2,
}
}
} // End if
return { result: true } // Safe to proceed
}
Explanation: By returning result: false, the tool fails gracefully. It sends the message back to the AI. The AI can then read this message and decide what to do (e.g., commit the files, or ask the user for permission to discard).
So, how does the user actually delete the worktree if they want to throw away the files?
They use the optional parameter we defined in Chapter 1:
{ "action": "remove", "discard_changes": true }
When discard_changes is true, our code in validateInput skips the safety checks entirely.
In this chapter, we added a critical layer of defense:
validateInput to block the remove action if changes exist.discard_changes flag to allow intentional cleanup.Now that our tool is safe to use, we can move on to the actual mechanics of "cleaning up." How do we actually delete the directory andβmore importantlyβhow do we put the AI back where it started?
Next Chapter: Worktree Lifecycle Actions
Generated by Code IQ