summaryrefslogtreecommitdiff
path: root/internal/executor
AgeCommit message (Collapse)Author
2026-03-12feat: add Resume support for CANCELLED, FAILED, and BUDGET_EXCEEDED tasksClaudomator Agent
Interrupted tasks (CANCELLED, FAILED, BUDGET_EXCEEDED) now support session resume in addition to restart. Both buttons are shown on the task card. - executor: extend resumablePoolStates to include CANCELLED, FAILED, BUDGET_EXCEEDED - api: extend handleResumeTimedOutTask to accept all resumable states with state-specific resume messages; replace hard-coded TIMED_OUT check with a resumableStates map - web: add RESUME_STATES set; render Resume + Restart buttons for interrupted states; TIMED_OUT keeps Resume only - tests: 5 new Go tests (TestResumeInterrupted_*); updated task-actions.test.mjs with 17 tests covering dual-button behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11feat: require agents to write a final summary before exitingClaudomator Agent
Add a mandatory '## Final Summary' section to planningPreamble instructing agents to output a 2-5 sentence summary paragraph (headed by '## Summary') as their last output before exiting. Adds three tests to verify the section and its required content are present in the preamble. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11fix: resume BLOCKED tasks in preserved sandbox so Claude finds its sessionPeter Stone
When a task ran in a sandbox (/tmp/claudomator-sandbox-*) and went BLOCKED, Claude stored its session under the sandbox path as the project slug. The resume execution was running in project_dir, causing Claude to look for the session in the wrong project directory and fail with "No conversation found". Fix: carry SandboxDir through BlockedError → Execution → resume execution, and run the resume in that directory so the session lookup succeeds. - BlockedError gains SandboxDir field; claude.go sets it on BLOCKED exit - storage.Execution gains SandboxDir (persisted via new sandbox_dir column) - executor.go stores blockedErr.SandboxDir in the execution record - server.go copies SandboxDir from latest execution to the resume execution - claude.go uses e.SandboxDir as working dir for resume when set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10test: sandbox coverage + fix WebSocket racesPeter Stone
executor: add 7 tests for sandboxCloneSource, setupSandbox, and teardownSandbox (uncommitted-changes error, clean-no-commits removal). api: fix two data races in WebSocket tests — wsPingInterval/Deadline are now captured as locals before goroutine start; maxWsClients is moved from a package-level var into Hub.maxClients (with SetMaxClients method) so concurrent tests don't stomp each other. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10executor: fix session ID on second block-and-resume cyclePeter Stone
When a resumed execution is blocked again, SessionID was set to the new exec's own UUID instead of the original ResumeSessionID. The next resume would then pass the wrong --resume argument to claude and fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10executor: explicit load balancing — code picks agent, classifier picks modelPeter Stone
pickAgent() deterministically selects the agent with the fewest active tasks, skipping rate-limited agents. The classifier now only selects the model for the pre-assigned agent, so Gemini gets tasks from the start rather than only as a fallback when Claude's quota is exhausted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10fix: ensure tasks are re-classified on manual restartPeter Stone
Updated handleRunTask to use ResetTaskForRetry, which clears the agent type and model. This ensures that manually restarted tasks are always re-classified, allowing the system to switch to a different agent if the previous one is rate-limited. Also improved Claude quota-exhaustion detection.
2026-03-10executor: extract handleRunResult to deduplicate error-classification logicClaudomator Agent
Both execute() and executeResume() shared ~80% identical post-run logic: error classification (BLOCKED, TIMED_OUT, CANCELLED, BUDGET_EXCEEDED, FAILED), state transitions, result emission, and UpdateExecution. Extract this into handleRunResult(ctx, t, exec, err, agentType) on *Pool. Both functions now call it after runner.Run() returns. Also adds TestHandleRunResult_SharedPath which directly exercises the new function via a minimalMockStore, covering FAILED, READY, COMPLETED, and TIMED_OUT classification paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09executor: unblock parent task when all subtasks completeClaudomator Agent
Add maybeUnblockParent helper that transitions a BLOCKED parent task to READY once every subtask is in the COMPLETED state. Called in both execute() and executeResume() immediately after a subtask is marked COMPLETED. Any non-COMPLETED sibling (RUNNING, FAILED, etc.) keeps the parent BLOCKED. Tests added: - TestPool_Submit_LastSubtask_UnblocksParent - TestPool_Submit_NotLastSubtask_ParentStaysBlocked - TestPool_Submit_ParentNotBlocked_NoTransition
2026-03-09executor: BLOCKED→READY for top-level tasks with subtasksClaudomator Agent
When a top-level task (ParentTaskID == "") finishes successfully, check for subtasks before deciding the next state: - subtasks exist → BLOCKED (waiting for subtasks to complete) - no subtasks → READY (existing behavior, unchanged) This applies to both execute() and executeResume(). Adds ListSubtasks to the Store interface. Tests: - TestPool_Submit_TopLevel_WithSubtasks_GoesBlocked - TestPool_Submit_TopLevel_NoSubtasks_GoesReady Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09executor: log errors from all unchecked UpdateTaskState/UpdateTaskQuestion callsClaudomator Agent
All previously ignored errors from p.store.UpdateTaskState() and p.store.UpdateTaskQuestion() in execute() and executeResume() now log with structured context (taskID, state, error). Introduces a Store interface so tests can inject a failing mock store. Adds TestPool_UpdateTaskState_DBError_IsLoggedAndResultDelivered to verify that a DB write failure is logged and the result is still delivered to resultCh. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09executor: document kill-goroutine safety and add goroutine-leak testClaudomator Agent
The pgid-kill goroutine in execOnce() uses a select with both ctx.Done() and the killDone channel. Add a detailed comment explaining why the goroutine cannot block indefinitely: the killDone arm fires unconditionally when cmd.Wait() returns (whether the process exited naturally or was killed), so the goroutine always exits before execOnce() returns. Add TestExecOnce_NoGoroutineLeak_OnNaturalExit to verify this: it samples runtime.NumGoroutine() before and after execOnce() with a no-op binary ("true") and a background context (never cancelled), asserting no net goroutine growth. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09executor: strengthen rate-limit avoidance in classifierPeter Stone
Updated isQuotaExhausted to detect more Claude quota messages. Added 'rate limit reached (rejected)' to quota exhausted checks. Strengthened classifier prompt to explicitly forbid selecting rate-limited agents. Improved Pool to set 5h rate limit on quota exhaustion.
2026-03-09executor: fix sandbox teardown — remove working copy pull, retry push on ↵Peter Stone
concurrent rejection - Remove git pull into project_dir: working copy is the developer workspace and should be pulled manually; www-data can't write to root-owned .git/objects - On non-fast-forward push rejection (concurrent task pushed first), fetch and rebase then retry once instead of failing the entire task
2026-03-09executor: fix map leaks in activePerAgent and rateLimitedClaudomator Agent
activePerAgent: delete zero-count entries after decrement so the map doesn't accumulate stale keys for agent types that are no longer active. rateLimited: delete entries whose deadline has passed when reading them (in both the classifier block and the execute() pre-flight), so stale entries are cleaned up on the next check rather than accumulating forever. Both fixes are covered by new regression tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09executor: update classifier model list to Claude 4.x and current Gemini modelsPeter Stone
2026-03-09executor: recover stale RUNNING tasks on server startupPeter Stone
On restart, any tasks in RUNNING state have no active goroutine. RecoverStaleRunning() marks them FAILED (retryable) and closes their open execution records with an appropriate error message. Called once from serve.go after the pool is created.
2026-03-09executor: fix Claude rate-limit detection and prioritize Gemini when limitedPeter Stone
Updated parseStream to detect 'rate_limit_event' and 'assistant' error:rate_limit messages from the Claude CLI. Updated Classifier to strongly prefer non-rate-limited agents. Added logging to Pool to track rate-limit status during classification.
2026-03-08executor: update gemini model to 2.5-flash-lite and fix classifier parsingPeter Stone
Update the default Gemini model and classification prompt to use gemini-2.5-flash-lite, which is the current available model. Improved the classifier's parsing logic to correctly handle the JSON envelope returned by the gemini CLI (stripping 'response' wrapper and 'Loaded cached credentials' noise).
2026-03-08executor: push sandbox commits via bare repo, pull into working copyPeter Stone
Instead of git fetch/merge INTO the working copy (which fails with mixed-owner .git/objects), clone FROM a bare repo, push BACK to it, then pull into the working copy: sandbox clone ← bare repo (local remote or origin) agent commits in sandbox git push sandbox → bare repo git pull bare repo → working copy sandboxCloneSource() prefers a remote named "local" (local bare repo), then "origin", then falls back to the working copy path. Set up: git remote add local /site/git.terst.org/repos/claudomator.git The bare repo was created with: git clone --bare /workspace/claudomator Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08executor: fix sandbox git fetch + inject prior failure historyPeter Stone
Fix: use file:// prefix in git fetch during sandbox teardown to force pack-protocol transfer. The local optimization uses hard links which fail across devices and with mixed-owner object stores. Feature: before running a task, query prior failed/timed-out executions and prepend their error messages to the agent's --append-system-prompt. This tells the agent what went wrong in previous attempts so it doesn't repeat the same mistakes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08executor: add git discipline section to agent preamblePeter Stone
Agents running in a sandbox must commit all changes before exiting. The teardown rejects any dirty working tree. Add an explicit section to the planning preamble making this requirement clear. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08fix(executor): use --no-hardlinks for sandbox git clonePeter Stone
git clone --local fails with "Invalid cross-device link" when /workspace and /tmp are on different filesystems. --no-hardlinks forces object copying instead, which works across devices. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08merge: pull latest from master and resolve conflictsPeter Stone
- Resolve conflicts in API server, CLI, and executor. - Maintain Gemini classification and assignment logic. - Update UI to use generic agent config and project_dir. - Fix ProjectDir/WorkingDir inconsistencies in Gemini runner. - All tests passing after merge.
2026-03-08feat(executor): implement Gemini-based task classification and load balancingPeter Stone
- Add Classifier using gemini-2.0-flash-lite to automatically select agent/model. - Update Pool to track per-agent active tasks and rate limit status. - Enable classification for all tasks (top-level and subtasks). - Refine SystemStatus to be dynamic across all supported agents. - Add unit tests for the classifier and updated pool logic. - Minor UI improvements for project selection and 'Start Next' action.
2026-03-08executor: internal dispatch queue; remove at-capacity rejectionPeter Stone
Replace the at-capacity error return from Submit/SubmitResume with an internal workCh/doneCh channel pair. A dispatch() goroutine blocks waiting for a free slot and launches the worker goroutine, so tasks are buffered up to 10x pool capacity instead of being rejected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08feat: rename working_dir→project_dir; git sandbox executionPeter Stone
- ClaudeConfig.WorkingDir → ProjectDir (json: project_dir) - UnmarshalJSON fallback reads legacy working_dir from DB records - New executions with project_dir clone into a temp sandbox via git clone --local - Non-git project_dirs get git init + initial commit before clone - After success: verify clean working tree, merge --ff-only back to project_dir, remove sandbox - On failure/BLOCKED: sandbox preserved, path included in error message - Resume executions run directly in project_dir (no re-clone) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08refactor: address code review notes (backward compat, Gemini tests, unknown ↵Peter Stone
agent test)
2026-03-08refactor(executor): update runners and tests for generic agentsPeter Stone
2026-03-08fix: detect quota exhaustion from stream; map to BUDGET_EXCEEDED not FAILEDPeter Stone
When claude hits the 5-hour usage limit it exits 1. execOnce was returning the generic "exit status 1" error, hiding the real cause from the retry loop and the task state machine. Fix: - execOnce now surfaces streamErr when it indicates rate limiting or quota exhaustion, so callers see the actual message. - New isQuotaExhausted() detects "hit your limit" messages — these are not retried (retrying a depleted 5h bucket wastes nothing but is pointless), and map to BUDGET_EXCEEDED in both execute/executeResume. - isRateLimitError() remains for transient throttling (429/overloaded), which continues to trigger exponential backoff retries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08feat(executor): implement GeminiRunnerPeter Stone
2026-03-08feat(executor): support multiple runners in PoolPeter Stone
2026-03-08test(executor): update claude_test.go to use AgentConfigPeter Stone
2026-03-07agent: lower breakdown threshold to 3min; use claudomator create CLI for ↵Peter Stone
subtasks Replaces the API POST instructions with the claudomator create command, which is simpler and consistent with how operators queue tasks. --start is explicitly omitted so subtasks are queued but not auto-started. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06fix: implement cancel endpoint and pool cancel mechanismPeter Stone
POST /api/tasks/{id}/cancel now works. Pool tracks a cancel func per running task ID; Cancel(taskID) calls it and returns false if the task isn't running. The execute goroutine registers/deregisters the cancel func around the runner call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06feat: blocked task state for agent questions via session resumePeter Stone
When an agent needs user input it writes a question to $CLAUDOMATOR_QUESTION_FILE and exits. The runner detects the file and returns BlockedError; the pool transitions the task to BLOCKED and stores the question JSON on the task record. The user answers via POST /api/tasks/{id}/answer. The server looks up the claude session_id from the most recent execution and submits a resume execution (claude --resume <session-id> "<answer>"), freeing the executor slot entirely while waiting. Changes: - task: add StateBlocked, transitions RUNNING→BLOCKED, BLOCKED→QUEUED - storage: add session_id to executions, question_json to tasks; add GetLatestExecution and UpdateTaskQuestion methods - executor: BlockedError type; ClaudeRunner pre-assigns --session-id, sets CLAUDOMATOR_QUESTION_FILE env var, detects question file on exit; buildArgs handles --resume mode; Pool.SubmitResume for resume path - api: handleAnswerQuestion rewritten to create resume execution - preamble: add question protocol instructions for agents - web: BLOCKED state badge (indigo), question text + option buttons or free-text input with Submit on the task card footer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05executor: default permission_mode to bypassPermissionsPeter Stone
claudomator runs tasks unattended; prompting for write permission always stalls execution. Any task without an explicit permission_mode now gets --permission-mode bypassPermissions automatically. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05executor: persist log paths at execution create time, not just at endPeter Stone
Add LogPather interface; ClaudeRunner implements it via ExecLogDir(). Pool pre-populates stdout_path/stderr_path/artifact_dir on the execution record before CreateExecution, so paths are in the DB from the moment a task starts running. ClaudeRunner.Run() skips path assignment when already set by the pool. Also update scripts/debug-execution to derive paths from the known convention (<data-dir>/executions/<exec-id>/) as a fallback for historical records that predate this change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05executor: detect stream-level failures when claude exits 0Peter Stone
Rename streamAndParseCost → parseStream (returns float64, error). Detect two failure modes that claude reports via exit 0: - result message with is_error:true - tool_result permission denial ("haven't granted it yet") Also fix cost extraction to read total_cost_usd from the result message (the actual field name), keeping cost_usd as legacy fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05Rescue work from claudomator-work: question/answer, ratelimit, start-next-taskPeter Stone
Merges features developed in /site/doot.terst.org/claudomator-work (a stale clone) into the canonical repo: - executor: QuestionRegistry for human-in-the-loop answers, rate limit detection and exponential backoff retry (ratelimit.go, question.go) - executor/claude.go: process group isolation (SIGKILL orphans on cancel), os.Pipe for reliable stdout drain, backoff retry on rate limits - api/scripts.go: POST /api/scripts/start-next-task handler - api/server.go: startNextTaskScript field, answer-question route, BroadcastQuestion for WebSocket question events - web: Cancel/Restart buttons, question banner UI, log viewer, validate section, WebSocket auto-connect All tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04Add READY state for human-in-the-loop verificationPeter Stone
Top-level tasks now land in READY after successful execution instead of going directly to COMPLETED. Subtasks (with parent_task_id) skip the gate and remain COMPLETED. Users accept or reject via new API endpoints: POST /api/tasks/{id}/accept → READY → COMPLETED POST /api/tasks/{id}/reject → READY → PENDING (with rejection_comment) - task: add StateReady, RejectionComment field, update ValidTransition - storage: migrate rejection_comment column, add RejectTask method - executor: route top-level vs subtask to READY vs COMPLETED - api: /accept and /reject handlers with 409 on invalid state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03Fix working_dir failures: validate path early, remove hardcoded /rootPeter Stone
executor/claude.go: stat working_dir before cmd.Start() so a missing or inaccessible directory surfaces as a clear error ("working_dir \"/bad/path\": no such file or directory") rather than an opaque chdir failure wrapped in "starting claude". api/elaborate.go: replace the hardcoded /root/workspace/claudomator path with buildElaboratePrompt(workDir) which injects the server's actual working directory (from os.Getwd() at startup). Empty workDir tells the model to leave working_dir blank. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03Executor: dependency waiting and planning preamblePeter Stone
- Pool.waitForDependencies polls depends_on task states before running - ClaudeRunner prepends planningPreamble to task instructions to prompt a plan-then-implement approach - Rate-limit test helper updated to match new ClaudeRunner signature Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24Add --verbose flag to Claude subprocess invocationPeter Stone
Ensures richer stream-json output for cost parsing and debugging. Adds a test to verify --verbose is always present in built args. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-08Rename Go module to github.com/thepeterstone/claudomatorPeter Stone
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08Initial project: task model, executor, API server, CLI, storage, reporterPeter Stone
Claudomator automation toolkit for Claude Code with: - Task model with YAML parsing, validation, state machine (49 tests, 0 races) - SQLite storage for tasks and executions - Executor pool with bounded concurrency, timeout, cancellation - REST API + WebSocket for mobile PWA integration - Webhook/multi-notifier system - CLI: init, run, serve, list, status commands - Console, JSON, HTML reporters with cost tracking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>