Repository navigation
fix(projects): deliver the first-turn prompt for headless-created projects - #410
Merged
Merged
Conversation
…jects
A project created via the API by the conductor (no browser) came up to an
idle Claude TUI and never started working (conv-160): activity stuck at
`waiting`, anthropic_hits=0. Two root causes:
1. The create body had no `prompt` field, yet the corral-api skill documents
`POST /projects/create -d '{"prompt":"..."}'` and promises it's auto-
submitted. The backend silently dropped it. Now honored: a caller prompt
is persisted as the project's PendingPrompt and overrides the generic
project.start template.
2. Prompt delivery was BROWSER-driven only — the UI calls POST /populate-prompt
after create. Headless callers have no browser, so nothing delivered it.
Now the backend auto-delivers on start (handleStartProject): if a
PendingPrompt exists it types it into Claude's tmux pane and submits.
Delivery requires tmux, so create forces LaunchTmux whenever a prompt is
pending (else the launcher runs a bare interactive claude with no pane to
send-keys into — the idle-forever failure).
Delivery is fire-once via claimPendingPrompt (atomic read-and-clear under a
mutex), so the browser's concurrent /start + /populate-prompt can't double-
type the task, and a later restart won't re-inject it. The tmux send logic
is extracted into deliverPromptToClaude, shared by both paths.
Tests: claimPendingPrompt fire-once + 16-way concurrent (exactly one winner,
-race clean) + missing/empty config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ompts The corral-api skill implied /projects/create alone starts Claude on the task. It doesn't — you must POST /p/<id>/start, which now auto-delivers the prompt. Spell out the two-step flow so the conductor doesn't leave a project idle (the conv-160 confusion). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
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.
The bug (conv 160 / 162)
When the conductor creates a project via the API and starts it, the sandbox's Claude comes up to an idle TUI and never starts working — activity stuck at
waiting,anthropic_hits=0. The whole "conductor routes code work into a sandbox" flow is broken end-to-end for anything it creates itself.Two root causes:
The create body had no
promptfield — yet thecorral-apiskill documentsPOST /projects/create -d '{"prompt":"..."}'and promises it's auto-submitted. The backend silently dropped it. The conductor passed the task inprompt; it went nowhere.Prompt delivery was browser-driven only. The UI calls
POST /p/<id>/populate-promptafter create to type the prompt into Claude's tmux pane. Headless callers have no browser → nothing delivered it. (And the container often wasn't even launched with tmux, so there was no pane to send to.)The fix
prompton create: persisted as the project'sPendingPrompt, overriding the genericproject.starttemplate when provided.handleStartProjecttypes the pending prompt into Claude's pane and submits it — no browser needed.LaunchTmux=truein that case (otherwise the launcher runs a bare interactiveclaudewith no pane → idle forever).claimPendingPromptatomically reads-and-clears under a mutex, so the browser's concurrent/start+/populate-promptcan't double-type the task, and a restart won't re-inject it. The tmux send logic is extracted intodeliverPromptToClaude, shared by both paths.Tests
claimPendingPrompt: fire-once, 16-way concurrent (exactly one winner,-raceclean), missing/empty config. Fullinternal/dashboard+internal/configbuild & pass.🤖 Generated with Claude Code