Repository navigation
Description
Summary
When a kimi-code process with live background tasks is still running and a second process resumes the same session, markLoadedTasksLost marks the first process's non-terminal tasks as lost — without any liveness probe, even though the task's PID is persisted on disk. The resume_hint returned to the model then explicitly instructs Agent(resume=agent_id) on task.lost, spawning a second concurrent worker on the same task while the first is still alive.
We hit this in production: two subagent instances ran concurrently for ~65 minutes, each editing the same file, each reporting "another session is modifying this file" — they were seeing each other. Both eventually completed; the task record had marked one of them lost while it was demonstrably alive.
Evidence (all paths verified against current master)
packages/agent-core-v2/src/agent/task/taskService.ts:854-869—markLoadedTasksLostiterates ghost tasks and marks every non-terminal one aslostand writes it back to disk. There is noprocess.kill(pid, 0)-style probe anywhere in this path.packages/agent-core-v2/src/agent/task/persist.ts:236,276— the persisted task record already containspid(LegacyPersistedTask.pid, mapped intoProcessTaskInfo), so the material for a liveness check is on disk, unused.packages/agent-core-v2/src/session/subagent/tools/agent.ts:481—resume_hintliststask.lostas a recovery case and tells the model to callAgent(resume=agent_id), which makes duplicate workers a scripted outcome rather than an accident.- There is no per-session cross-process lock, so two processes resuming the same session (e.g. a supervisor that spawns one CLI process per message with
--resume) race: the live one's tasks get markedlost, and the task JSON itself is last-writer-wins between the two processes.
This is the same "killed but not reaped" family as #1882 (torn wire.jsonl after SIGKILL), one layer up in the stack.
Suggested fixes (in order of effort)
- Probe before marking lost (small): in
markLoadedTasksLost, forkind === 'process'ghosts, runprocess.kill(pid, 0). If the process is alive, keep the taskrunningand skip the lost notification. A ready-made probe pattern exists inapps/kap-server/src/lock.ts(PID + liveness check for instance locks). - Fix the guidance (trivial): change
resume_hintto state thatlostdoes not mean the worker is dead, and that the caller should verify the old instance is gone before resuming. - Per-session cross-process lock (design-level): owner PID + heartbeat, reusing the
kap-server/src/lock.tspattern.reconcile()skips lost-marking while the lock owner is alive. This also covers agent-kind tasks, which have no PID to probe (same-process loops).
We'd be happy to contribute (1) as a PR if the direction sounds right.
Environment
- kimi-code 0.27.0, agent-core-v2
- Linux, supervisor spawning one CLI process per message with
--resume(cc-connect)
Activity
fix(agent-core): probe pid liveness before marking restored tasks lost