diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-11 07:40:48 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-11 07:40:48 +0000 |
| commit | 1b5e7177769c79f9e836a55f9c008a295e2ff975 (patch) | |
| tree | e0660a68884ed1309095e61b91b857b0cdb4005c /internal/executor/claude.go | |
| parent | 23f9b65bf65b3d3677350a456e57294a4df810b9 (diff) | |
fix: resume BLOCKED tasks in preserved sandbox so Claude finds its session
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>
Diffstat (limited to 'internal/executor/claude.go')
| -rw-r--r-- | internal/executor/claude.go | 17 |
1 files changed, 13 insertions, 4 deletions
diff --git a/internal/executor/claude.go b/internal/executor/claude.go index 4839a90..0e29f7f 100644 --- a/internal/executor/claude.go +++ b/internal/executor/claude.go @@ -32,6 +32,7 @@ type ClaudeRunner struct { type BlockedError struct { QuestionJSON string // raw JSON from the question file SessionID string // claude session to resume once the user answers + SandboxDir string // preserved sandbox path; resume must run here so Claude finds its session files } func (e *BlockedError) Error() string { return fmt.Sprintf("task blocked: %s", e.QuestionJSON) } @@ -98,10 +99,16 @@ func (r *ClaudeRunner) Run(ctx context.Context, t *task.Task, e *storage.Executi } // For new (non-resume) executions with a project_dir, clone into a sandbox. - // Resume executions run directly in project_dir to pick up the previous session. + // Resume executions run in the preserved sandbox (e.SandboxDir) so Claude + // finds its session files under the same project slug. If no sandbox was + // preserved (e.g. task had no project_dir), fall back to project_dir. var sandboxDir string effectiveWorkingDir := projectDir - if projectDir != "" && e.ResumeSessionID == "" { + if e.ResumeSessionID != "" { + if e.SandboxDir != "" { + effectiveWorkingDir = e.SandboxDir + } + } else if projectDir != "" { var err error sandboxDir, err = setupSandbox(projectDir) if err != nil { @@ -137,8 +144,10 @@ func (r *ClaudeRunner) Run(ctx context.Context, t *task.Task, e *storage.Executi data, readErr := os.ReadFile(questionFile) if readErr == nil { os.Remove(questionFile) // consumed - // Preserve sandbox on BLOCKED — agent may have partial work. - return &BlockedError{QuestionJSON: strings.TrimSpace(string(data)), SessionID: e.SessionID} + // Preserve sandbox on BLOCKED — agent may have partial work and its + // Claude session files are stored under the sandbox's project slug. + // The resume execution must run in the same directory. + return &BlockedError{QuestionJSON: strings.TrimSpace(string(data)), SessionID: e.SessionID, SandboxDir: sandboxDir} } // Merge sandbox back to project_dir and clean up. |
