[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XCHVNFD7KD7RWCHT2N3H1F":3,"skill-same:01M6XCHVNFD7KD7RWCHT2N3H1F":78},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":59,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M6XCHVNFD7KD7RWCHT2N3H1F","cl_async-task-ux",null,"Use when adding a long-running background task to Canary Lab (anything that shouldn't block a REST request or the UI) — coverage generation, port-ification, spec\u002Feval generation.","ferterahadi\u002Fcanary-lab","ferterahadi","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F148174839?v=4","01M6XCHCY39C7QEF3JZJ3VZGNZ","Fer Terahadi","01M6XCHD1ZE9926NBC97ZMZZRY",4,"MIT","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcl_async-task-ux\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"ux","cl async task ux","cl","async task","task","async","use","coverage","eval","generation","long","port","block","canary","lab","request","REST","when","running","spec","2026-11-10 10:12:30",false,"---\nname: cl_async-task-ux\ndescription: Use when adding a long-running background task to Canary Lab (anything that shouldn't block a REST request or the UI) — coverage generation, port-ification, spec\u002Feval generation.\n---\n\n# Canary Lab Async-Task UX\n\nLong-running work (agent runs, multi-second generation, double-boot verification)\nmust NOT block a REST handler or freeze the UI. Canary models it as a **background\njob**: start returns immediately, the job persists to disk, the UI\u002FMCP poll it, and\na server restart recovers it. Get this contract right by copying an existing impl —\ndon't invent a new lifecycle.\n\n## Reference implementations\n\n| Subsystem | Store | Runner \u002F lifecycle |\n| --- | --- | --- |\n| Verified Coverage jobs | `apps\u002Fweb-server\u002Fsrc\u002Ffeatures\u002Fcoverage\u002Flogic\u002Fcoverage\u002Fjobs\u002Fstore.ts` | `…\u002Fjobs\u002Frunner.ts` (`startCoverageJob`) |\n| Port-ification | `apps\u002Fweb-server\u002Fsrc\u002Ffeatures\u002Fportify\u002Flogic\u002Fruntime\u002Fstore.ts` | `…\u002Fportify\u002F*` + `src\u002Ffeatures\u002Fportify\u002Froutes\u002Fportify.ts` |\n\nCoverage jobs are the smallest complete example — read those two files first.\n\n## The five-part contract\n\n1. **Non-blocking.** The start endpoint validates input, creates a `running`\n   manifest, kicks off the work *detached* (no `await` on the driver), and returns\n   promptly with the manifest. Coverage returns `202`; Portify currently returns\n   `200`. The driver streams progress into the manifest and\n   flips it to `done` \u002F `failed` when it settles.\n2. **Persistent.** State lives in a file-backed store (`\u003Clogs>\u002F\u003Ckind>\u002F\u003Cid>\u002F…json`\n   + an `index.json`), never only in memory. The coverage store uses the shared\n   `FileBackedTaskStore`; its `save()` writes atomically (tmp + rename) then\n   emits a `changed` event (the WS\u002Fpoll push point). Reads come\n   straight off disk so a fresh process sees history.\n3. **Recoverable.** On server boot, call `store.reconcileInterrupted(now)` to flip\n   any job left `running` by the dead process to `aborted` — otherwise it shows as\n   live forever AND wedges the single-flight lock. Wire this next to the other\n   store reconciles in `server.ts`.\n4. **Re-openable.** Status is a pure read of the manifest by id plus a list\n   endpoint (`GET \u002Fapi\u002Fcoverage\u002Fjobs\u002F:jobId` and\n   `GET \u002Fapi\u002Ffeatures\u002F:name\u002Fcoverage\u002Fjobs` for coverage; `GET \u002Fapi\u002Fportify\u002F:workflowId`\n   and `GET \u002Fapi\u002Fportify` for Portify). The dialog\u002Fpill re-attaches to a\n   running job by polling that id — closing the UI never kills the work.\n5. **Server-side single-flight.** The guard is on the START path, keyed on the\n   real identity (e.g. `feature + kind`): `store.activeFor(...)` → throw a typed\n   conflict (`409`). **UI disabling is cosmetic, never the guard** — two tabs, an\n   agent, and the UI all hit the same on-disk index, so the server must be the lock.\n\n## Dual-surface parity\n\nEvery async capability ships on BOTH the UI (REST) and MCP, against the same store\n(see `cl_add-mcp-tool`). Start + poll + list at minimum; accept\u002Freject-style\nfollow-ups if the job parks for human input. MCP tools may construct a fresh store\nover the same `logsDir` — the lock is the on-disk index, not the instance.\n\n## Common mistakes\n\n| Mistake | Consequence |\n| --- | --- |\n| `await`-ing the driver in the start handler | Request blocks — the whole point is lost |\n| Single-flight enforced only by disabling the button | A second tab \u002F an agent \u002F a restart races a duplicate job |\n| Skipping `reconcileInterrupted` on boot | Crashed jobs show as live forever and hold the lock |\n| In-memory-only state | A restart loses history and re-openability |\n| Non-atomic manifest writes | A crash mid-write corrupts the job; readers throw |\n","# Canary Lab Async-Task UX\n\nLong-running work (agent runs, multi-second generation, double-boot verification)\nmust NOT block a REST handler or freeze the UI. Canary models it as a **background\njob**: start returns immediately, the job persists to disk, the UI\u002FMCP poll it, and\na server restart recovers it. Get this contract right by copying an existing impl —\ndon't invent a new lifecycle.\n\n## Reference implementations\n\n| Subsystem | Store | Runner \u002F lifecycle |\n| --- | --- | --- |\n| Verified Coverage jobs | `apps\u002Fweb-server\u002Fsrc\u002Ffeatures\u002Fcoverage\u002Flogic\u002Fcoverage\u002Fjobs\u002Fstore.ts` | `…\u002Fjobs\u002Frunner.ts` (`startCoverageJob`) |\n| Port-ification | `apps\u002Fweb-server\u002Fsrc\u002Ffeatures\u002Fportify\u002Flogic\u002Fruntime\u002Fstore.ts` | `…\u002Fportify\u002F*` + `src\u002Ffeatures\u002Fportify\u002Froutes\u002Fportify.ts` |\n\nCoverage jobs are the smallest complete example — read those two files first.\n\n## The five-part contract\n\n1. **Non-blocking.** The start endpoint validates input, creates a `running`\n   manifest, kicks off the work *detached* (no `await` on the driver), and returns\n   promptly with the manifest. Coverage returns `202`; Portify currently returns\n   `200`. The driver streams progress into the manifest and\n   flips it to `done` \u002F `failed` when it settles.\n2. **Persistent.** State lives in a file-backed store (`\u003Clogs>\u002F\u003Ckind>\u002F\u003Cid>\u002F…json`\n   + an `index.json`), never only in memory. The coverage store uses the shared\n   `FileBackedTaskStore`; its `save()` writes atomically (tmp + rename) then\n   emits a `changed` event (the WS\u002Fpoll push point). Reads come\n   straight off disk so a fresh process sees history.\n3. **Recoverable.** On server boot, call `store.reconcileInterrupted(now)` to flip\n   any job left `running` by the dead process to `aborted` — otherwise it shows as\n   live forever AND wedges the single-flight lock. Wire this next to the other\n   store reconciles in `server.ts`.\n4. **Re-openable.** Status is a pure read of the manifest by id plus a list\n   endpoint (`GET \u002Fapi\u002Fcoverage\u002Fjobs\u002F:jobId` and\n   `GET \u002Fapi\u002Ffeatures\u002F:name\u002Fcoverage\u002Fjobs` for coverage; `GET \u002Fapi\u002Fportify\u002F:workflowId`\n   and `GET \u002Fapi\u002Fportify` for Portify). The dialog\u002Fpill re-attaches to a\n   running job by polling that id — closing the UI never kills the work.\n5. **Server-side single-flight.** The guard is on the START path, keyed on the\n   real identity (e.g. `feature + kind`): `store.activeFor(...)` → throw a typed\n   conflict (`409`). **UI disabling is cosmetic, never the guard** — two tabs, an\n   agent, and the UI all hit the same on-disk index, so the server must be the lock.\n\n## Dual-surface parity\n\nEvery async capability ships on BOTH the UI (REST) and MCP, against the same store\n(see `cl_add-mcp-tool`). Start + poll + list at minimum; accept\u002Freject-style\nfollow-ups if the job parks for human input. MCP tools may construct a fresh store\nover the same `logsDir` — the lock is the on-disk index, not the instance.\n\n## Common mistakes\n\n| Mistake | Consequence |\n| --- | --- |\n| `await`-ing the driver in the start handler | Request blocks — the whole point is lost |\n| Single-flight enforced only by disabling the button | A second tab \u002F an agent \u002F a restart races a duplicate job |\n| Skipping `reconcileInterrupted` on boot | Crashed jobs show as live forever and hold the lock |\n| In-memory-only state | A restart loses history and re-openability |\n| Non-atomic manifest writes | A crash mid-write corrupts the job; readers throw |\n",{"name":5,"description":7},"main","2c85305cab69e5f773d59ea79a64a0b049a98345",".claude\u002Fskills\u002Fcl_async-task-ux",0,"https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab","https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fcanary-lab","https:\u002F\u002Fgithub.com\u002Fferterahadi","https:\u002F\u002Fgithub.com\u002Fferterahadi-oddle","Shipping well-tested, production-ready, vibe-coded code using today’s latest form of engineering - harness, agentic, loop, graph, or whatever drops next","2026-11-05 11:59:56",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add ferterahadi\u002Fcanary-lab --skill cl_async-task-ux",{"tool":70,"command":71},"bunx","bunx skills add ferterahadi\u002Fcanary-lab --skill cl_async-task-ux",{"tool":73,"command":74},"pnpm","pnpm dlx skills add ferterahadi\u002Fcanary-lab --skill cl_async-task-ux","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcl_async-task-ux\u002FSKILL.md",true,[],[79,90,100,115,141,161],{"skillNo":80,"name":81,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":88,"publishedAt":49},"01M6XCWFJ75NNVF0N8C0XGNC17","off-by-one","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fapps\u002Fweb-server\u002Fprompts\u002Fsabotage-skills\u002Foff-by-one\u002Fskill.md",[84,85],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[89],"off by one",{"skillNo":91,"name":92,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":97,"publishedAt":49},"01M6XCWBYW1QHVFKSDQCCRRRDT","multi-failure-cascade","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fapps\u002Fweb-server\u002Fprompts\u002Fsabotage-skills\u002Fmulti-failure-cascade\u002Fskill.md",[95,96],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},[98,99],"multi","multi failure cascade",{"skillNo":101,"name":102,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":110,"publishedAt":49},"01M6XCW8XA7QY74Q18D9PGFFMD","broken-delete-contract","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fapps\u002Fweb-server\u002Fprompts\u002Fsabotage-skills\u002Fbroken-delete-contract\u002Fskill.md",[105,106,107],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":108,"categoryName":109,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000037","律师与法务",[111,112,113,114],"delete","broken","contract","broken delete contract",{"skillNo":116,"name":117,"nameZh":6,"description":118,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":123,"publishedAt":49},"01M6XCW439KA486AN2QZACNYTC","canary-lab","Use when the user wants Canary Lab to take a product repo end to end — \"test this app\", \"onboard this repo\", \"run a flight\", \"evaluate what I just built\" — through the flight pipeline (start_flight \u002F get_flight \u002F respond_flight_checkpoint over MCP). One conducted pipeline continues a configured suite or takes bare repo(s) to a green, covered, healed run and publishes a downloadable Report; Parallel setup continues as Canary-owned background work. Use a focused skill only when the user asks for that single capability without asking for a Flight or \u002Fcanary-lab.","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fplugin\u002Fcanary-lab\u002Fskills\u002Fcanary-lab\u002FSKILL.md",[121,122],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},[43,42,124,125,126,127,128,129,130,131,132,133,134,135,136,137,46,138,139,140],"canary lab","capability","pipeline","work","only","take","green","get","end","app","respond","suite","product","bare","start","MCP","run",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":149,"publishedAt":49},"01M6XCVYQ31KC4ZXCD5QMRKXEE","canary-lab-verify","Use when verifying a Canary Lab feature against a RUNNING target — a deployed environment (\"verify checkout on staging\", \"run the saved verify config\", \"check the deployed app\") or a locally booted app (\"verify a running app\") — through the verify MCP tools (list\u002Fget\u002Fcreate\u002Fupdate_verification_config, boot_services, execute_verification, get_verification_result). No heal loop. For run + heal use canary-lab-run; for end-to-end onboarding use canary-lab (flight).","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fplugin\u002Fcanary-lab\u002Fskills\u002Fcanary-lab-verify\u002FSKILL.md",[147,148],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},[42,150,43,151,124,131,139,152,153,140,154,155,156,157,158,132,133,159,160,46],"verify","canary lab verify","config","create","update","verification","Tools","loop","list","checkout","result",{"skillNo":162,"name":163,"nameZh":6,"description":164,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":165,"categories":166,"keywords":172,"publishedAt":49},"01M6XCVSWW705CN3C69FQMPZXN","canary-lab-run","Use when running or healing an EXISTING Canary Lab feature locally — \"run \u003Cfeature>\", \"fix the failing run\", \"rerun 7cvh\", \"drive the heal loop\" — through the repair MCP tools (start_run, wait_for_heal_task, signal_run, get_heal_context, pause_run, cancel_heal, abort_run). Fix failing runs by editing app\u002Fservice code, not tests. For end-to-end onboarding of a new repo use canary-lab (flight); for deployed-environment checks use canary-lab-verify.","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fplugin\u002Fcanary-lab\u002Fskills\u002Fcanary-lab-run\u002FSKILL.md",[167,168,169],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":170,"categoryName":171,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师",[173,124,140,42,43,132,131,174,175,157,156,138,176,139,46,177,178,133,179,180],"canary lab run","fix","context","code","rerun","wait","new repo","failing"]