[세션] 칸 프로필이 바뀐 뒤 옛 세션이 주 세션으로 되살아나는 결함 #25
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
실례 4096b726(10-04): ① 작업이 10-03 생성 때 고정 프로필(Gemini 3.8 Flash High e2eaaa5c)을 실행 설정(metadata.agent_profile_id·deferred_launch)으로 들고 있었고, 14:22 R2 관제가 update_task_kandev 로 deferred_launch_prompt 만 고쳤는데 그 예약 실행이 바로 발화해 「선행 대기」(에이전트 없는 칸)에서 Gemini 세션 713f31b2 가 떴다(한도로 첫 턴 종료). ② 14:26 「계획」 진입 때 칸 프로필(구현 상급 (동적) 35909ac3)로 새 세션 dc5efd0f 가 생겨 주 세션이 됐다(관제의 list_task_sessions 14:27
14:35 is_primary 확인). 옛 Gemini 세션은 끝나지 않고 WAITING_FOR_INPUT 으로 남았다. ③ 14:3515:50 사이 주 세션 표시가 713f31b2 로 되돌아갔다 — 자동 승격(maybePromotePrimary)·감시·화면의 primary 변경 기록이 하나도 없다. 남는 경로: 전체 행 갱신(session.go UpdateTaskSession 의is_primary = ?)이 호출자 메모리 사본의 IsPrimary 를 그대로 써 넣는다 — 옛 사본(713f31b2 가 주 세션이던 때)이 다시 쓰이면 되살아난다(추정 — 재현으로 확정할 것). ④ 15:50 감시가 대기열 메시지를 그 「주 세션」에 옮겨 넣어 Gemini 세션을 깨웠고, 일반 메시지 「Usage Limit Reached」 뒤 RUNNING 으로 27분 칸·세션 자리를 쥐었다(그 부분은 W-014·감시 수정). 고칠 것: (a) 전체 행 갱신에서 is_primary 를 쓰지 않는다 — metadata 처럼 SetSessionPrimary 경로로만 바꾼다(이미 주석에 metadata 사본 덮어쓰기 위험이 적혀 있다). 재현 시험으로 ③ 을 확정한다. (b) 칸 프로필 전환으로 새 세션을 만들면 다른 프로필의 옛 세션은 끝냄(park/complete)으로 정리해 주 세션 후보가 되지 않게 하고, 주 세션 대체 승격(maybePromotePrimary·promoteNextPrimaryAfterRemoval)은 지금 칸 프로필 세션을 먼저 고르며 다른 프로필의 옛 세션은 고르지 않는다. (c) 에이전트 없는 칸(선행 대기·보류 등)에 있는 작업은 실행 설정만 고쳐도 예약 실행을 발화하지 않는다 — 에이전트 칸에 들어갈 때 그 칸 프로필로 시작한다. 확인: 위 순서를 재현하는 시험에서 주 세션이 칸 프로필 세션으로 유지되고 옛 세션이 깨지 않는지. 지금 R2(컷 전)에 넣는다 — 소유자 「즉시 해야 하는 작업이면 kandev 관제가 작업을 R2 로 옮기는 게 맞지」. 감시 임시 보호: 받는 세션이 칸 프로필이고 주 세션이 다른 프로필이면 옮기지 않고 받는 세션을 주 세션으로 되돌린다(kandev_watchdog.py, 백업 .bak-20261004-primaryrestore).PR #71 merged into fork main as a6412141d9 (fix(task): stop a stale session write from resurrecting an old primary), 2026-10-04T12:08:41Z.
Done: is_primary removed from the full-row session update; automatic primary selection now prefers the current workflow step effective profile while each caller keeps its prior eligibility (maybePromotePrimary active-only, promoteNextPrimaryAfterRemoval excludes terminal); an agentless step entry retains the deferred launch record instead of spending it, threaded through autoStartTaskForLoadedStep as an explicit parameter, and a step that cannot be read still gets the launch attempt.
Evidence: reviewed head 65eff5f61a merged unchanged; local verification in the task worktree: go build -tags fts5 exit 0, narrow orchestrator / sqlite / mcp handlers tests ok, golangci-lint --new-from-rev=5998036951 0 issues, gofmt clean. Shared checkout main fast-forwarded to origin/main (e8c162c491 -> a6412141d9).
Remaining: residual risk unchanged, ceiling replay still launches with the payload creation-time profile (deliberately out of PR scope). Deployment readback for the 4096b726 sequence could not be registered directly on b3eb8c3c: message_task_kandev timed out twice (MCP -32001) and that session sits in a provider-limit recovery state, so the registration request went to the parent control task 3360477d instead.
Follow-ups: register the deployment readback on b3eb8c3c when that task session recovers; no task is blocked by this one.