-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Claude Permission Daemon - Remote approval via Slack #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Added comprehensive implementation plan to the feature document including: - 6 milestones covering foundation, core components, Slack integration, hook script, deployment, and acceptance criteria - Specific tasks with commit message prefix format (Init - M.T) - Latest dependency versions from PyPI research (slack-bolt 1.27.0, aiohttp 3.13.3, pytest 9.x, pytest-asyncio 1.3.0, pytest-cov 7.0.0) - Progress tracking checkboxes for each task - Exit criteria for each milestone Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added pyproject.toml with: - Project metadata and Python 3.14 requirement - Dependencies: slack-bolt>=1.27.0, aiohttp>=3.13.0 - Dev dependencies: pytest>=9.0.0, pytest-asyncio>=1.3.0, pytest-cov>=7.0.0 - Entry points for daemon and hook scripts - pytest and coverage configuration - Created src/claude_permission_daemon/__init__.py with version - Created directory structure for tests, systemd, and example Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configuration loading module with: - DaemonConfig: socket_path, idle_timeout, request_timeout - SlackConfig: bot_token, app_token, channel with validation - SwayidleConfig: binary path - Config.load() classmethod for TOML parsing - Environment variable overrides (CLAUDE_PERM_* prefix) - Validation method returning list of errors Uses Python 3.11+ tomllib from stdlib (no external dependency). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
State management module with: - Action enum: APPROVE, DENY, PASSTHROUGH - PermissionRequest dataclass: request_id, tool_name, tool_input, timestamp - PermissionResponse dataclass: action, reason, to_dict() - PendingRequest dataclass: tracks request, hook_writer, Slack message info - StateManager class: - Async-safe via asyncio.Lock - idle property with callbacks on state change - CRUD operations for pending requests - update_slack_info() for tracking Slack messages - clear_all_pending() for race condition handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added example/config.toml with: - All configuration options documented - Default values shown in comments - Instructions for Slack token setup - Notes on environment variable overrides Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added test infrastructure: - tests/__init__.py - tests/conftest.py with fixtures: - temp_dir: temporary directory for tests - sample_config_content/config_file: full config fixtures - minimal_config_content/minimal_config_file: minimal config fixtures - tests/test_config.py: 15 tests covering DaemonConfig, SlackConfig, SwayidleConfig, and Config loading/validation/env overrides - tests/test_state.py: 24 tests covering Action, PermissionRequest, PermissionResponse, PendingRequest, and StateManager All 39 tests pass. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Milestone 1 complete with all 5 tasks: - 1.1: Project skeleton (pyproject.toml, __init__.py, directories) - 1.2: config.py (TOML loading, env var overrides, validation) - 1.3: state.py (dataclasses, StateManager with async locks) - 1.4: example/config.toml - 1.5: pytest infrastructure (39 tests passing) Updated progress log in feature document. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
IdleMonitor class for managing swayidle subprocess: - Spawns swayidle with timeout/resume commands - Reads stdout asynchronously for IDLE/ACTIVE messages - Tracks current idle state with callback on changes - Graceful shutdown with terminate/kill fallback - Binary path resolution (PATH search or explicit) - Error handling and logging throughout - restart() method for recovery Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SocketServer class for Unix domain socket communication: - Accepts connections from hook scripts - Parses JSON requests with tool_name and tool_input - Creates PermissionRequest and passes to handler with writer - Supports deferred responses (writer passed to handler) - Socket permissions set to 0600 (user-only) - Graceful shutdown with connection cleanup - send_response() helper for sending and closing Protocol: newline-delimited JSON for both request and response. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Main daemon orchestration module: - Daemon class coordinating IdleMonitor and SocketServer - asyncio.gather for running components concurrently - Signal handling (SIGTERM, SIGINT) for graceful shutdown - StateManager integration with idle change callbacks - Permission request handling (passthrough for now) - _resolve_request() for sending responses to hooks - Race condition handling (user returns while pending) - CLI with --config, --debug, --version options - Logging configuration with debug mode Slack integration is stubbed for Milestone 3. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added comprehensive tests for Milestone 2 components: test_idle_monitor.py (15 tests): - Initial state, command building, binary resolution - Output handling (IDLE/ACTIVE/unknown) - Start/stop lifecycle with mocked subprocess - Restart behavior and state reset - Read loop with mocked stdout test_socket_server.py (15 tests): - Initial state, socket creation and permissions - Start/stop lifecycle and cleanup - Valid request handling with response - Error cases (invalid JSON, missing fields) - send_response helper with PermissionResponse and dict All 69 tests passing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Milestone 2 complete with all 4 tasks: - 2.1: idle_monitor.py (swayidle subprocess management) - 2.2: socket_server.py (Unix socket for hook connections) - 2.3: daemon.py shell (orchestration, signal handling) - 2.4: Unit tests (30 new tests, 69 total passing) Updated progress log in feature document. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SlackHandler class with Socket Mode connection: - AsyncApp with slack-bolt for Socket Mode - start/stop lifecycle management - post_permission_request() to send messages - update_message_* methods for state changes - Action handlers registered for approve/deny buttons Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Integrated SlackHandler into daemon orchestration: - Import and initialize SlackHandler - Add slack_handler.run() to asyncio tasks - Stop Slack handler during shutdown - _handle_slack_action() callback for button clicks - Post to Slack when user is idle - Update Slack messages on approve/deny/answered locally - Handle Slack failures with passthrough fallback All 69 tests still passing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added comprehensive tests for slack_handler module (20 tests): TestFormatPermissionRequest: - Bash command formatting with buttons and request_id - File operation formatting with path and content - Description field inclusion - Long content truncation TestFormatApproved/Denied/AnsweredLocally: - Correct header text and emoji - Context message verification TestSlackHandler: - Initial state, start/stop lifecycle - run() without start raises error - post_permission_request without app returns None - Action handlers call callback with correct params TestSlackHandlerWithMockedApp: - Successful permission request posting - Posting failure handling - Message update methods (approved/denied/answered locally) - Update methods without app (no-op) All 89 tests passing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Milestone 3 complete with all 5 tasks: - 3.1: slack_handler.py base (Socket Mode, AsyncApp) - 3.2: Block Kit messages (request, approved, denied, answered locally) - 3.3: Button action handlers (approve/deny with request_id) - 3.4: Wire Slack into daemon (full integration) - 3.5: Unit tests (20 new tests, 89 total passing) Updated progress log in feature document. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Standalone hook script for Claude Code permission requests: - Uses only Python stdlib (no external dependencies) - Reads JSON request from stdin - Connects to daemon via Unix socket - Sends request and waits for response (5min timeout) - Outputs JSON for approve/deny, no output for passthrough - Graceful fallback to passthrough on any error Entry point registered in pyproject.toml as claude-permission-hook. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Daemon orchestration was already completed in task 3.4. This commit adds egg-info to .gitignore and confirms the race condition handling: - _on_idle_change() resolves pending requests on user return - Slack messages updated to "Answered Locally" - Hook receives passthrough response for local handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added comprehensive integration tests (17 tests): TestSocketServerIntegration: - Full request/response flow through socket - Multiple concurrent connections handling - Deferred response (simulating Slack wait) TestStateManagerIntegration: - Idle callback trigger on state changes - Pending request lifecycle (add, update, remove) - Clear pending with callback (race condition simulation) TestHookScript: - Passthrough when daemon not running - Format output for approve/deny/passthrough/unknown - Read request from stdin (empty, valid, invalid) TestEndToEndFlow: - Complete approve flow - Complete deny flow - Complete passthrough flow All 106 tests passing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Milestone 4 complete with all 3 tasks: - 4.1: hook.py (stdlib only, socket client, JSON output) - 4.2: daemon orchestration (already done in M3) - 4.3: Integration tests (17 new tests, 106 total passing) Updated progress log in feature document. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added systemd user service file with: - ExecStart pointing to ~/.local/bin/claude-permission-daemon - Automatic restart on failure - Security hardening (NoNewPrivileges, ProtectSystem, etc.) - ReadWritePaths for XDG_RUNTIME_DIR socket - ReadOnlyPaths for config directory - WAYLAND_DISPLAY environment for swayidle Install with: cp systemd/*.service ~/.config/systemd/user/ systemctl --user daemon-reload systemctl --user enable --now claude-permission-daemon Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive README with: - Overview and key features - Requirements (Python 3.14+, Wayland, Slack) - Installation instructions: - Package installation - Slack app setup (detailed steps) - Configuration file setup - Systemd service installation - Claude Code hook configuration - Configuration reference (all options documented) - Environment variable overrides - How it works explanation - Troubleshooting section - Development instructions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Final acceptance tasks completed: - Documentation review: All source files have proper docstrings, README is comprehensive - Test coverage verification: 106 tests, 60% overall coverage (98-100% for core modules) - Full test suite passes - Feature moved to completed Feature implementation is now complete. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New feature to: - Add unit tests for daemon.py module - Create GitHub Actions workflow for CI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Plan includes: - Milestone 1: Daemon unit tests (6 tasks) - Milestone 2: GitHub Actions workflow (2 tasks) - Milestone 3: Acceptance criteria (3 tasks) Target: Improve coverage from 60% to 75%+ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for Daemon.__init__: - Creates StateManager - Stores config - Components are None before start - Shutdown event not set - Tasks list empty Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests with mocked components: - start() creates and starts all components - start() registers idle callback - stop() cancels tasks - stop() stops all components - stop() sends passthrough to pending requests - request_shutdown() sets event Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for _handle_permission_request and _resolve_request: - Active user gets passthrough immediately - Idle user gets request posted to Slack - Slack failure results in passthrough - Missing Slack handler results in passthrough - Resolve with approve/deny - Unknown request ID doesn't raise error Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for _handle_slack_action: - Approve action updates message and sends approve response - Deny action updates message and sends deny response - Unknown request ID doesn't raise error - Request without Slack info still gets response Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for _on_idle_change (race condition logic): - Going idle doesn't resolve pending requests - Becoming active resolves pending with passthrough - Becoming active updates Slack message to 'answered locally' - Multiple pending requests all get resolved - No pending requests is fine Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests for setup_logging: - Default INFO level - Debug mode - Third-party logger noise reduction Tests for parse_args: - Default values - Config option (-c, --config) - Debug option (-d, --debug) - Combined options Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Workflow test-permission-daemon.yml: - Runs on push to claude_permission_daemon/** - Runs on PR to main - Runs on workflow_dispatch (manual) - Uses Python 3.14 - Installs dev dependencies - Runs pytest with coverage - Uploads coverage report artifact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Final results: - Coverage improved from 60% to 75% (target met) - daemon.py: 0% -> 77% - 142 tests passing (36 new daemon tests) - Feature moved to completed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements the Claude Permission Daemon, a Python-based daemon that enables remote approval of Claude Code permission requests via Slack when the user is idle.
Features Implemented
1. Initial Implementation (Milestones 1-6)
2. Test Coverage & CI (TestCI feature)
Key Architectural Decisions
asyncio.gather()for concurrent componentsTest Results
Test plan
🤖 Generated with Claude Code