Problem
Detaching the last skill from a workspace never removes it from bound projects. The stale bundle stays on disk indefinitely.
parsePage in packages/opencode/src/altimate/workspace/skill-sync.ts rejected pages < 1 as a malformed envelope. But the server sends exactly this for an empty workspace:
{"items":[],"total":0,"page":1,"size":50,"pages":0}
So pages: 0 was read as error, the caller kept whatever was on disk, and zero skills became unobservable. The remote.length === 0 → removeManaged branch behind it was therefore unreachable dead code.
Reproduction
Verified live against production during workspace e2e:
- Attach a skill to a workspace, sync it into a bound project — bundle lands.
- Detach it in the SaaS.
GET /skills?datamate_id=… now returns total: 0, pages: 0.
- Run the agent again. The bundle is still on disk — confirmed unchanged across three further turns.
Why tests did not catch it
Every fixture standing in for a healthy empty workspace used pages: 1, a shape the server never sends. One test listed 0 among malformed values and asserted it must not purge, locking the behaviour in.
Fix
Accept pages: 0 only when the rest of the envelope agrees it is empty (items: [] and total: 0); pages: 0 alongside rows is still refused as inconsistent. 0 removed from the malformed list, two real cases added. Mutation-tested — reverting the fix fails exactly the new assertion.
Problem
Detaching the last skill from a workspace never removes it from bound projects. The stale bundle stays on disk indefinitely.
parsePageinpackages/opencode/src/altimate/workspace/skill-sync.tsrejectedpages < 1as a malformed envelope. But the server sends exactly this for an empty workspace:{"items":[],"total":0,"page":1,"size":50,"pages":0}So
pages: 0was read as error, the caller kept whatever was on disk, and zero skills became unobservable. Theremote.length === 0 → removeManagedbranch behind it was therefore unreachable dead code.Reproduction
Verified live against production during workspace e2e:
GET /skills?datamate_id=…now returnstotal: 0, pages: 0.Why tests did not catch it
Every fixture standing in for a healthy empty workspace used
pages: 1, a shape the server never sends. One test listed0among malformed values and asserted it must not purge, locking the behaviour in.Fix
Accept
pages: 0only when the rest of the envelope agrees it is empty (items: []andtotal: 0);pages: 0alongside rows is still refused as inconsistent.0removed from the malformed list, two real cases added. Mutation-tested — reverting the fix fails exactly the new assertion.