Created
Jun 24, 05:26
Started
Jun 24, 06:52
Completed
Jun 24, 13:45
DevOps handoff
Type
Bug
Shape
backend
Worktree Slug
mascot-marker-no-downgrade-fallback
Repositories
mcritchie-studio
Release Train
—
Branch
feat/mascot-marker-no-downgrade-fallback
Local URL
—
QA URL
—
Production URL
—
Acceptance Criteria
Expected Test Plan
Checks Run
Agent Context
Bug: bin/task's write_feature_marker copies the mascot straight from dv['mascot'] in a single API response. The board assigns the per-session mascot lazily (Task#sync_session_mascot runs on create AND re-derives on every build-phase transition), so a given create/move response can momentarily lack the mascot. When it does, the per-session marker (.agents/sessions/<id>.json) is written WITHOUT a mascot, and bin/statusline drops from the preferred '⊙ <Mascot> · app · feature' form to its no-mascot fallback ('app · feature · <task URL> [stage]') — the flip-flop the operator observed for a projects-root (non-worktree) session. bin/agent-worktree.resolve_mascot already has a no-downgrade fallback chain (TASK_MASCOT -> last-good on-disk -> board via 'task field'), added in d0a976c to fix the WORKTREE path; write_feature_marker never got the equivalent. Fix: give write_feature_marker the same guarantee — resolve mascot via response.devops.mascot -> existing on-disk marker value -> board GET, never overwriting a known mascot with blank. Extract the pure first-present pick into lib/feature_marker.rb (mirrors lib/claim_lease.rb) for a unit test; integration-test the end-to-end marker write via the TaskCliTest stub-server harness. Also make PROJECTS_DIR honor CLAUDE_PROJECTS_DIR (bin/statusline already does) so the integration test writes its marker to an isolated dir; no prod behavior change when the env is unset.
Stage Timeline
Who handled each stage, the time it took (measured), and the model / tokens / cost reported (best-effort) — plus who's on it right now. — means the agent didn't report that metric.
Conversation
QA review feedback, agent handoffs, and follow-up notes for this task.
Review cascade: shannon[light] APPROVE; carl[heavy] BLOCK.
carl[heavy]: board_mascot calls api(:get) in-process; on a non-2xx board read api->die!->exit 1 raises SystemExit, which is NOT StandardError, so it escapes board_mascot's rescue AND write_feature_marker's rescue — aborting the whole bin/task move/create with exit 1 AFTER the server op succeeded (false failure; churn under the rate-limit epic). REQUIRED: make board_mascot incapable of terminating the process — mirror bin/agent-worktree resolve_mascot (shell out: `bin/task field <slug> mascot 2>/dev/null` in a subprocess) OR rescue SystemExit/Exception->nil around the api call. ADD an integration test: board-fallback path returns non-2xx -> command still exits 0 AND writes the marker. (Also: checks_run says unit(7); actual 6 tests/9 assertions.)
Rework verified (7c270b03): board_mascot rewritten to read via subprocess (`task field <slug> mascot 2>/dev/null`, shellescaped) mirroring resolve_mascot — SystemExit from a non-2xx board read is now contained in the child, can never abort bin/task. + board-fallback regression test (fail_get knob → non-2xx → asserts exit 0 + marker written + no mascot). RED->GREEN (1 fail → 32/115 + 6/9). dor-check ✓. carl[heavy] block resolved; shannon[light] had approved. Merge-gated on CI test green.
Sealed-bid sizing
Edit →Alex (PM)
—
Avi (PO)
—
Dev
—
Actual
—
We emailed a one-tap sign-in link to . It expires shortly and can only be used once.
No email? Check spam, or close this and try again.