Alex Netsch ee212c5e61 Add Claude Code Stop hook for uncommitted changes check (Vibe Kanban) (#2049)
* The implementation is complete. Here's a summary of the changes:

## Summary

I added a check for commits made by the coding agent during execution. If commits were made, the cleanup script will now run.

### Changes Made

**File: `crates/local-deployment/src/container.rs`**

1. **Added new method `has_commits_from_execution`** (lines 314-344): This method compares the `before_head_commit` (captured at execution start) with the current HEAD for each repo to detect if any commits were made during execution.

2. **Updated the `should_start_next` logic** (lines 477-490): The decision to run the cleanup script now checks both:
   - Whether uncommitted changes were just committed (`changes_committed`)
   - Whether the agent made any commits during execution (`has_commits_from_execution`)

   If either condition is true, the cleanup script runs.

### How It Works

- Before: Cleanup script only ran if `try_commit_changes()` returned `true` (i.e., if there were uncommitted changes to commit)
- After: Cleanup script runs if `try_commit_changes()` returned `true` **OR** if `has_commits_from_execution()` returns `true` (i.e., HEAD changed from what it was at execution start)

This ensures that if a coding agent makes commits during its execution but leaves no uncommitted changes, the cleanup script will still run.

* feat: add Claude Code hooks for cleanup scripts in worktrees

When a workspace is created with repos that have cleanup scripts configured,
automatically create .claude/settings.local.json with a "Stop" hook that
runs the cleanup scripts when the agent finishes.

- Add create_claude_hooks_config() to create .claude/hooks/run-cleanup.sh
- Handle single-repo (agent in repo dir) vs multi-repo (agent in workspace root)
- Also add has_commits_from_execution() to run cleanup when agent made commits

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* All tests pass. The fix is complete - the Claude Code hooks will now always be created regardless of whether repos have cleanup scripts configured.

* Cleanup script changes for workspace bf914d08-cb51-49a4-89b9-ba56108c841a

* Done. I've implemented the simplified `check-git-status.sh` hook using `include_str!`:

1. Created `crates/local-deployment/src/scripts/check-git-status.sh` - the script that checks for uncommitted changes and blocks the agent until they commit

2. Updated `crates/local-deployment/src/container.rs`:
   - Added `const CHECK_GIT_STATUS_SCRIPT: &str = include_str!("scripts/check-git-status.sh");` to embed the script at compile time
   - Simplified `create_claude_hooks_config()` to only write this one hook script (removed all the cleanup script logic)

The hook will now be created in `.claude/hooks/check-git-status.sh` and configured in `.claude/settings.local.json` as a "Stop" hook for Claude Code workspaces.

* Done. I've implemented the Stop hook via the protocol in `claude.rs`:

**Summary of changes:**

1. **Created** `crates/executors/src/scripts/check-git-status.sh` - The script that checks for uncommitted changes

2. **Modified** `crates/executors/src/executors/claude.rs`:
   - Added `const CHECK_GIT_STATUS_SCRIPT` using `include_str!` to embed the script at compile time
   - Updated `get_hooks()` to always include a Stop hook with the script (using `shlex::try_quote` for proper escaping)
   - The Stop hook is now sent via `protocol_peer.initialize(hooks)` alongside any PreToolUse hooks

3. **Removed** from `crates/local-deployment/src/container.rs`:
   - Deleted `create_claude_hooks_config()` function
   - Deleted the const and call to create hooks
   - Deleted the `scripts/` directory

Now the hook is sent via the protocol, so no files are created in the workspace that would show up as untracked.

* Done. I've updated the implementation to use `settings.local.json` with the script inlined via `bash -c`:

**Summary of changes:**

1. **Reverted `crates/executors/src/executors/claude.rs`**:
   - Removed `CHECK_GIT_STATUS_SCRIPT` const
   - Restored `get_hooks()` to only return PreToolUse hooks (no Stop hook)

2. **Moved script to `crates/local-deployment/src/scripts/check-git-status.sh`**

3. **Added `shlex = "1.3.0"` to `crates/local-deployment/Cargo.toml`**

4. **Updated `crates/local-deployment/src/container.rs`**:
   - Added `CHECK_GIT_STATUS_SCRIPT` const using `include_str!`
   - Added `create_claude_hooks_config()` function that:
     - Only runs for single-repo workspaces
     - Creates `.claude/settings.local.json` with the script inlined via `bash -c` using `shlex::try_quote` for proper escaping
     - No `.claude/hooks/` directory is created (script is fully inline)
   - Called from `create_workspace_config_files()`

Since `settings.local.json` is already gitignored by Claude Code, this approach avoids the untracked file issue while keeping the script in a separate file for maintainability.

* Cleanup script changes for workspace bf914d08-cb51-49a4-89b9-ba56108c841a

* Done. The Claude hooks setup now only runs for Claude Code executor.

**Changes made to `crates/local-deployment/src/container.rs`:**

1. Removed the call to `create_claude_hooks_config()` from `create_workspace_config_files()` (and changed parameter back to `_agent_working_dir`)

2. Added the hooks setup in `start_execution_inner()` with a check for Claude Code executor:
   ```rust
   if let Some(BaseCodingAgent::ClaudeCode) = executor_action.base_executor() {
       Self::create_claude_hooks_config(
           &current_dir,
           workspace.agent_working_dir.as_deref(),
       )
       .await?;
   }
   ```

Now the `settings.local.json` with the Stop hook will only be created when running with the Claude Code executor, not for other executors like Codex, Gemini, etc.

* Cleanup script changes for workspace bf914d08-cb51-49a4-89b9-ba56108c841a

* Done. The implementation now explicitly checks the repo count:

**Changes to `crates/local-deployment/src/container.rs`:**

1. **Updated call site in `start_execution_inner()`:**
   ```rust
   if let Some(BaseCodingAgent::ClaudeCode) = executor_action.base_executor() {
       let repos = WorkspaceRepo::find_repos_for_workspace(&self.db.pool, workspace.id).await?;
       if repos.len() == 1 {
           if let Some(agent_dir) = &workspace.agent_working_dir {
               Self::create_claude_hooks_config(&current_dir.join(agent_dir)).await?;
           }
       }
   }
   ```

2. **Simplified `create_claude_hooks_config()`:**
   - Now takes just `repo_dir: &Path` (the full path to the repo directory)
   - Removed the `agent_working_dir` parameter and internal check
   - The caller is responsible for ensuring this is only called for single-repo workspaces

* Cleanup script changes for workspace bf914d08-cb51-49a4-89b9-ba56108c841a

* Done. I've successfully implemented the Stop hook via protocol callbacks instead of the file-based approach. Here's a summary of the changes:

## Summary

**Files modified:**
1. `crates/executors/src/executors/claude/client.rs`:
   - Added `working_dir: PathBuf` field to `ClaudeAgentClient`
   - Added `STOP_GIT_CHECK_CALLBACK_ID` constant
   - Updated `on_hook_callback()` to handle Stop git check callbacks
   - Added `check_git_status()` async function that checks for uncommitted changes
   - Added `find_git_repo_in_subdirs()` helper for multi-repo fallback

2. `crates/executors/src/executors/claude.rs`:
   - Updated `get_hooks()` to always include a Stop hook with callback ID
   - Passed `working_dir` to `ClaudeAgentClient::new()` in `spawn_internal()`
   - Added import for `STOP_GIT_CHECK_CALLBACK_ID`

3. `crates/local-deployment/src/container.rs`:
   - Removed `CHECK_GIT_STATUS_SCRIPT` constant
   - Removed `create_claude_hooks_config()` function
   - Removed the call to create file-based hooks in `start_execution_inner()`

4. `crates/local-deployment/Cargo.toml`:
   - Removed `shlex` dependency

**Files deleted:**
- `crates/local-deployment/src/scripts/check-git-status.sh`
- `crates/local-deployment/src/scripts/` (empty directory)

**Benefits of the new approach:**
- No files created in the workspace (avoids untracked file issues)
- Git status check runs dynamically via protocol callback
- Supports single-repo workspaces with fallback to checking subdirectories
- More portable - no dependency on bash or external scripts

* Cleanup script changes for workspace bf914d08-cb51-49a4-89b9-ba56108c841a

* Done. I've fixed the Stop hook response format. The changes:

1. **`check_git_status()`** now returns the correct format for Stop hooks:
   - When allowing stop: returns `{}` (empty object)
   - When blocking stop: returns `{"continue": false, "stopReason": "..."}`

2. **`on_hook_callback()`** now returns the Stop hook result directly without wrapping it in `hookSpecificOutput`

The implementation now follows the Claude SDK documentation for Stop hooks, which use top-level `continue` and `stopReason` fields rather than the `hookSpecificOutput` wrapper used by PreToolUse hooks.

* Done. I've updated the Stop hook response format to use `{"decision": "approve"}` or `{"decision": "block", "reason": "..."}` which matches the SDK callback format documented at code.claude.com/docs/en/hooks.

* Fixed. Removed the unused `_agent_working_dir` parameter from `create_workspace_config_files` and updated both call sites.

* Done. I've implemented the `RepoContext` struct to properly pass repository information from local-deployment to executors. Here's a summary of the changes:

**Files modified:**

1. **`crates/executors/src/env.rs`**:
   - Added `RepoContext` struct with `workspace_root` and `repo_names` fields
   - Added `repo_paths()` method to get full paths to all repos
   - Added `repo_context: RepoContext` field to `ExecutionEnv` (required field)
   - Updated `ExecutionEnv::new()` to take `RepoContext` as parameter

2. **`crates/executors/src/executors/claude/client.rs`**:
   - Replaced `working_dir: PathBuf` with `repo_context: RepoContext` in `ClaudeAgentClient`
   - Updated `check_git_status()` to iterate over all repos and check each for uncommitted changes
   - Removed `find_git_repo_in_subdirs()` helper (no longer needed)

3. **`crates/executors/src/executors/claude.rs`**:
   - Updated `spawn_internal()` to pass `env.repo_context` to `ClaudeAgentClient`

4. **`crates/local-deployment/src/container.rs`**:
   - Added import for `RepoContext`
   - In `start_execution_inner()`: Load repos, build `RepoContext`, pass to `ExecutionEnv::new()`

* Done. I've added the `commit_reminder` setting to `ClaudeCode`:

1. **Added `commit_reminder: Option<bool>` field** to the `ClaudeCode` struct in `claude.rs`
2. **Made the Stop hook conditional** - it now only registers when `commit_reminder` is `true` (defaults to `false`/off)
3. **Regenerated TypeScript types** so the frontend knows about the new field

Users can enable the commit reminder by adding `"commit_reminder": true` to their profile configuration in `~/.config/vibe-kanban/profiles.json`.

* Done. Removed all 10 obvious comments as requested.

* Done. Now synthetic User messages (like the Stop hook feedback) will be rendered as `SystemMessage` entries in the logs. The implementation:

1. Added `is_synthetic: bool` field to `ClaudeJson::User` variant (with `#[serde(default, rename = "isSynthetic")]`)
2. When `is_synthetic` is true, any Text content in the message gets rendered as a `SystemMessage` entry

* Fix tests

* No findings in the latest commits I reviewed (048e3e1b5, 723ff78bd, c1caa1f73, 4af28b855, ad1334b94). Residual risk: I didn’t run tests, and the review is based on reading diffs only.

Open questions / assumptions:
- Assuming it’s intentional that the Stop hook is now opt-in via `commit_reminder` in `crates/executors/src/executors/claude.rs`.
- Assuming workspaces always populate repo names; otherwise the Stop hook in `crates/executors/src/executors/claude/client.rs` will auto-approve when none are configured.

Change summary:
- Added repo context plumbed into executor env and multi-repo git status checks in `crates/executors/src/env.rs`, `crates/executors/src/executors/claude.rs`, `crates/executors/src/executors/claude/client.rs`, and `crates/local-deployment/src/container.rs`.
- Introduced `commit_reminder` config and schema/types updates in `crates/executors/src/executors/claude.rs`, `shared/schemas/claude_code.json`, and `shared/types.ts`.
- Render synthetic user messages as system log entries in `crates/executors/src/executors/claude.rs`.

* Lint

* Done. I've added the `commit_reminder` beta setting to the global Config:

1. **Backend Config** (`v8.rs`) - Added `commit_reminder: bool` field (defaults to `false`)
2. **Removed from ClaudeCode** - Removed per-profile field, now uses global setting
3. **ExecutionEnv** - Added `commit_reminder` field to pass setting to executors
4. **Container** - Reads setting from config and passes to `ExecutionEnv`
5. **Claude executor** - Uses `env.commit_reminder` when building hooks
6. **Frontend UI** - Added checkbox in Beta Features section
7. **i18n** - Added translation keys for all 6 locales (en, es, ja, ko, zh-Hans, zh-Hant)
8. **TypeScript types** - Regenerated via `pnpm run generate-types`

Users can now enable the commit reminder feature from Settings > General > Beta Features.

* All tests pass. Both `pnpm run lint` and `cargo test --workspace` complete successfully.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 17:32:27 +00:00
2025-08-08 13:53:27 +01:00
2026-01-15 10:58:39 +00:00
2025-11-14 15:37:56 +00:00
2025-12-16 10:55:27 +00:00
2025-11-14 15:37:56 +00:00
2025-07-11 15:57:30 +01:00
2025-12-15 19:42:13 +00:00
2025-07-11 16:31:08 +01:00
2026-01-15 10:58:39 +00:00
2025-06-25 09:27:29 +01:00

Vibe Kanban Logo

Get 10X more out of Claude Code, Gemini CLI, Codex, Amp and other coding agents...

npm Build status Ask DeepWiki

We're hiring!

Overview

AI coding agents are increasingly writing the world's code and human engineers now spend the majority of their time planning, reviewing, and orchestrating tasks. Vibe Kanban streamlines this process, enabling you to:

  • Easily switch between different coding agents
  • Orchestrate the execution of multiple coding agents in parallel or in sequence
  • Quickly review work and start dev servers
  • Track the status of tasks that your coding agents are working on
  • Centralise configuration of coding agent MCP configs
  • Open projects remotely via SSH when running Vibe Kanban on a remote server

You can watch a video overview here.

Installation

Make sure you have authenticated with your favourite coding agent. A full list of supported coding agents can be found in the docs. Then in your terminal run:

npx vibe-kanban

Documentation

Please head to the website for the latest documentation and user guides.

Support

We use GitHub Discussions for feature requests. Please open a discussion to create a feature request. For bugs please open an issue on this repo.

Contributing

We would prefer that ideas and changes are first raised with the core team via GitHub Discussions or Discord, where we can discuss implementation details and alignment with the existing roadmap. Please do not open PRs without first discussing your proposal with the team.

Development

Prerequisites

Additional development tools:

cargo install cargo-watch
cargo install sqlx-cli

Install dependencies:

pnpm i

Running the dev server

pnpm run dev

This will start the backend. A blank DB will be copied from the dev_assets_seed folder.

Building the frontend

To build just the frontend:

cd frontend
pnpm build

Build from source (macOS)

  1. Run ./local-build.sh
  2. Test with cd npx-cli && node bin/cli.js

Environment Variables

The following environment variables can be configured at build time or runtime:

Variable Type Default Description
POSTHOG_API_KEY Build-time Empty PostHog analytics API key (disables analytics if empty)
POSTHOG_API_ENDPOINT Build-time Empty PostHog analytics endpoint (disables analytics if empty)
PORT Runtime Auto-assign Production: Server port. Dev: Frontend port (backend uses PORT+1)
BACKEND_PORT Runtime 0 (auto-assign) Backend server port (dev mode only, overrides PORT+1)
FRONTEND_PORT Runtime 3000 Frontend dev server port (dev mode only, overrides PORT)
HOST Runtime 127.0.0.1 Backend server host
MCP_HOST Runtime Value of HOST MCP server connection host (use 127.0.0.1 when HOST=0.0.0.0 on Windows)
MCP_PORT Runtime Value of BACKEND_PORT MCP server connection port
DISABLE_WORKTREE_ORPHAN_CLEANUP Runtime Not set Disable git worktree cleanup (for debugging)

Build-time variables must be set when running pnpm run build. Runtime variables are read when the application starts.

Remote Deployment

When running Vibe Kanban on a remote server (e.g., via systemctl, Docker, or cloud hosting), you can configure your editor to open projects via SSH:

  1. Access via tunnel: Use Cloudflare Tunnel, ngrok, or similar to expose the web UI
  2. Configure remote SSH in Settings → Editor Integration:
    • Set Remote SSH Host to your server hostname or IP
    • Set Remote SSH User to your SSH username (optional)
  3. Prerequisites:
    • SSH access from your local machine to the remote server
    • SSH keys configured (passwordless authentication)
    • VSCode Remote-SSH extension

When configured, the "Open in VSCode" buttons will generate URLs like vscode://vscode-remote/ssh-remote+user@host/path that open your local editor and connect to the remote server.

See the documentation for detailed setup instructions.

Description
Get 10X more out of Claude Code, Codex or any coding agent
https://www.vibekanban.com/
Readme Apache-2.0 48 MiB
Languages
Rust 51.1%
TypeScript 46.6%
CSS 1.1%
JavaScript 0.7%
PLpgSQL 0.2%
Other 0.2%