Console new session on a repo-less project gets
Epic 5410 (folder slots) shipped every surface except a NEW interactive console session on a project with Repo unticked (`has_repo=false`). PRD AC8 (task 5412) plus the operator's own answer: "everything will be the same, including web console".
Reproduction (static trace on develop ac9065ef7, done by the Finish runner 5434):
1. A folder project holds no static slot. `slot_allocation.static_target` returns 0 (apps/api/app/slot_allocation.py:~193), and `_converge_assigned` retires the statics (:~683).
2. Console open (`POST /api/console/{project}/{number}/open`, apps/api/app/routes/console.py:690) resolves an EXISTING numbered worktree row (`_resolve_console_node`, :647). It then claims that row through `_claim_slot_for_console_user` (:581), which calls `slot_state.claim_for_user`.
3. `slot_state.claim_for_user` (apps/api/app/slot_state.py:~190) filters on `Worktree.kind == WORKTREE_KIND_STATIC`, so a dynamic or folder row can never be user-claimed.
4. The manual declare route refuses folder projects with 409 "has no repository, so it holds no declared slot" (apps/api/app/routes/pool_slots.py:~394-401).
Result: an operator cannot open a new console session on a repo-less project. There is no row to open, no way to declare one, and no claim path. Attaching to a task's running session is a separate case (viewing, not claiming) and is out of scope unless it turns out broken too.
Fix: when the console creates a session on a `has_repo=false` project, mint a dynamic folder slot on an assigned node. Reuse the existing `slot_allocation.add_dynamic` / `lease_drain._take_dynamic` declare path with has_repo=false. The CP claims it for the user (state `user`) instead of for a task. On the user's `/exit` (`routes/agent.py` `user_session_ended`, :~6363), the slot is freed, and the existing dynamic free→destroy path (`slot_state.py:~466` → `_mint_destroy` → node `_destroy_folder_slot`) deletes the folder. Respect `pool_max` / headroom (`dynamic_headroom`) and `mixed_kind_wait`, and keep the org floor of `claim_for_user`. Add no new surface beyond what this needs. The web console's create action must offer this on a folder project. If the web UI currently hides "new session" for a project with no static slots, make it available for has_repo=false.
Out of scope: repo projects' console behaviour, which must stay byte-identical, including the static-only rule for repo projects. Hermetic `slot_mode=dynamic` environments. Any change to task-held folder slots, prompts, settle, or the Repo checkbox. Do not add a static folder pool.
Done means:
- AC-a: an API test creates a has_repo=false project assigned to a node, calls the console create/open path, and observes a new `worktrees` row with kind dynamic, has_repo false, state `user`, owner = the caller, plus a lease intent carrying has_repo:false. Oracle: run the new test and see it pass. It must fail on develop ac9065ef7 (mutation check: revert the fix and confirm it goes red).
- AC-b: an API test shows `/exit` (`user_session_ended`) on that slot frees it and mints a destroy intent carrying has_repo:false.
- AC-c: an API test shows a repo project's console claim still refuses dynamic rows (static-only, unchanged).
- AC-d: on dev.jaah.ai, open a new console on a repo-less project, run `pwd; ls -la; git status` (expect an empty dir and "not a git repository"), `/exit`, and observe the slot gone from the Nodes slot view. Screenshot it in the report. If dev is unreachable from your node, say so plainly in the report and do not claim AC-d.
- `./dc check full` green.
Outcome
Console "New session" on repo-less projects: creates a user-held dynamic folder slot, freed and destroyed on /exit. Repo projects unchanged. PR #4755 merged; CI gate and dev deploy green. AC-d not claimed: dev unreachable (task 5458).
Artifacts
Activity
- Ready
- Runner started
- Ready
- Runner started
- Runner ended (done)