diff --git a/apps/desktop/src/main/todo-agent/trpc-router.ts b/apps/desktop/src/main/todo-agent/trpc-router.ts index 05e679a8929..d288606741e 100644 --- a/apps/desktop/src/main/todo-agent/trpc-router.ts +++ b/apps/desktop/src/main/todo-agent/trpc-router.ts @@ -220,11 +220,12 @@ export const createTodoAgentRouter = () => { /** * Edit the user-authored fields (description / goal) of a TODO - * session that has not started yet. Allowed only in pre-start - * states (queued / failed / aborted / escalated) so a running - * worker's prompt can never mutate under its feet. When the - * description / goal changes we rewrite the session's goal.md - * so the next run picks up the edit. + * session. Allowed in queued / preparing / failed / aborted / + * escalated. `preparing` is safe because the supervisor has + * not spawned Claude yet and `prepareArtifacts` will rewrite + * goal.md before it is read. Refused once the session is + * running / verifying so the worker's prompt never mutates + * under its feet. */ updateFields: publicProcedure .input( @@ -251,6 +252,7 @@ export const createTodoAgentRouter = () => { } if ( session.status !== "queued" && + session.status !== "preparing" && session.status !== "failed" && session.status !== "aborted" && session.status !== "escalated" @@ -258,7 +260,7 @@ export const createTodoAgentRouter = () => { throw new TRPCError({ code: "PRECONDITION_FAILED", message: - "実行中またはキュー済みでないセッションは編集できません。中断してから再度お試しください。", + "実行中のセッションは編集できません。中断してから再度お試しください。", }); } const patch: { diff --git a/apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx b/apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx index b807cb53a15..aa1d349f95b 100644 --- a/apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx +++ b/apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx @@ -1030,7 +1030,25 @@ function SessionDetail({ session, onDeleted }: SessionDetailProps) { } }, [invalidate, rerunMut, session.id]); - const canEditFields = canStart && !isRunning; + // `preparing` is still editable: the supervisor has not spawned + // Claude yet, and prepareArtifacts rewrites goal.md before Claude + // reads it, so an edit during preparing still takes effect. + const canEditFields = canStart || session.status === "preparing"; + + // Bail out of any in-flight edit the moment the session starts + // actually running — e.g. a queued session whose turn arrived + // mid-edit — so the user doesn't hit Save only to get rejected by + // the backend guard. Only cancel on running/verifying; terminal + // states stay editable (rerun path). + useEffect(() => { + if (!editingField) return; + if (session.status !== "running" && session.status !== "verifying") return; + setEditingField(null); + setEditDraft(""); + toast.warning( + "タスクの実行が開始されたため編集を中止しました。中断してから再度編集してください。", + ); + }, [editingField, session.status]); const startEditField = useCallback( (field: "description" | "goal") => {