You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[bug] Cursor: every file Read sends a skill-use webhook with empty data #1238
On Cursor, every Read tool call sends a skill-use webhook, including reads of ordinary files. When the file is not a SKILL.md, the payload carries data: {}. A team subscribed to skill-use gets one notification per file read, and none of them names a skill.
Found while triaging #1236. It does not depend on #1236 and predates it.
Observed
Reproduced locally on macOS with the built CLI at main (8487c34). The team teamai.yaml had a webhook endpoint pointed at a local listener, and the host simulator ran the installed Cursor hook entries.
Cursor PostToolUse payload
track
webhook received
Read of .../skills/demo-skill/SKILL.md
records demo-skill
skill-use, data: {"skillName":"demo-skill"}
Read of README.md
records nothing
skill-use, data: {}
Shell
not dispatched
none
Claude Skill and Read calls behave correctly. Only the Skill call sends a webhook.
track filters through resolveSkillUse and returns when the read is not a skill.
webhookHandler (src/hook-handlers.ts) maps any PostToolUse to skill-use and always sends. buildWebhookData returns {} when resolveSkillUse finds no skill, but an empty result does not stop the send. The comment in buildWebhookData says it "never [fires] for a normal file Read". The behavior does not match that comment.
Expected
A skill-use webhook is sent only for a real skill use. This is the same rule track already applies.
Approach
One rule for "is this a skill use". The webhook should follow the same verdict track reaches through resolveSkillUse, so the two cannot drift apart. It should not get a second recognizer.
No skill means no event. An unrecognized payload must not be sent as skill-use with empty data.
Leave other webhook events alone.session-start and session-stop stay as they are.
This also matters for #1236. Merging the * and Skill dispatches there would move the webhook onto every tool call on every host, so this fix should land first or together with it.
Proof of done. With a webhook endpoint configured, a Cursor Read of an ordinary file sends nothing, and a Cursor Read of a SKILL.md and a Claude Skill call each send one skill-use with the skill name.
Hi! I'd like to pick this up, following the approach in the issue.
Plan: make the skill-use path in src/hook-handlers.ts report "no skill" instead of {} when resolveSkillUse finds nothing, and have webhookHandler return without sending in that case, so the webhook uses the same verdict as track and no second recognizer is added. session-start / session-stop stay as they are. I'll add unit tests for a Cursor Read of a SKILL.md (sends skill-use with skillName) and a Read of an ordinary file (sends nothing), and check the Claude Skill case still sends.
I work with AI assistance and will run lint, tsc --noEmit and vitest before opening the PR.
One addition to your checks. This change alters runtime behavior, so the PR needs an end-to-end record from the real CLI on top of the unit tests, lint and tsc. AGENTS.md requires it under "Testing before a PR". One representative run is enough. After npm run build, configure a webhook endpoint, run the Cursor hook dispatch, and paste the result into the PR description. A Cursor Read of an ordinary file must send nothing, and a Cursor Read of a SKILL.md must send one skill-use with the skill name. If you add an e2e test, npm run test:e2e -- <test-file> runs just that file.
This looks fixed on main by #1245 (22dfe27). webhookHandler now maps only session start/stop (WEBHOOK_EVENT_BY_HOOK in src/hook-handlers.ts has sessionstart, stop, sessionend), so a PostToolUse no longer becomes skill-use. The skill-use webhook is sent from skillUseHandler, once per skill creditToolCall returns, and a Read of README.md credits none.
I haven't re-run the hook simulator from the issue; worth one run of the README.md row before closing.
We ran the simulator from #1236 through the built CLI on macOS. On main (13c891e0) and on 0.28.0-beta.8, a Cursor Read of README.md sends no webhook, and a Cursor Read of a SKILL.md sends one skill-use with skillName. On 0.28.0-beta.7 the same README.md read sent skill-use with data: {}.
Only betas were affected. The Cursor Read mapping that caused it (#1157, 7b71316b) first shipped in 0.28.0-beta.5, and 0.28.0-beta.8 has the fix. 0.27.0 (latest) never sent the empty webhook, but it does not count a Cursor SKILL.md read as a skill use either, because its Cursor tool entry matches Skill, which Cursor never sends. Only /skill prompt mentions are counted there. The next stable release gets both. On a beta, upgrade to 0.28.0-beta.8 or later (npm i -g teamai-cli@0.28.0-beta.8) and run teamai pull once to replace the old hook entry; no configuration change is needed.
Closing as completed. Reopen if a Cursor read of a non-skill file still sends skill-use on 0.28.0-beta.8 or later.
Activity
wang2-lat commentedon Oct 20, 2026
Hi! I'd like to pick this up, following the approach in the issue.
Plan: make the
skill-usepath insrc/hook-handlers.tsreport "no skill" instead of{}whenresolveSkillUsefinds nothing, and havewebhookHandlerreturn without sending in that case, so the webhook uses the same verdict astrackand no second recognizer is added.session-start/session-stopstay as they are. I'll add unit tests for a CursorReadof aSKILL.md(sendsskill-usewithskillName) and aReadof an ordinary file (sends nothing), and check the ClaudeSkillcase still sends.I work with AI assistance and will run lint,
tsc --noEmitand vitest before opening the PR.SaulMoro commentedon Oct 20, 2026
Thanks @wang2-lat, the plan matches the issue.
One addition to your checks. This change alters runtime behavior, so the PR needs an end-to-end record from the real CLI on top of the unit tests, lint and
tsc. AGENTS.md requires it under "Testing before a PR". One representative run is enough. Afternpm run build, configure a webhook endpoint, run the Cursor hook dispatch, and paste the result into the PR description. A CursorReadof an ordinary file must send nothing, and a CursorReadof aSKILL.mdmust send oneskill-usewith the skill name. If you add an e2e test,npm run test:e2e -- <test-file>runs just that file.SaulMoro commentedon Oct 21, 2026
@wang2-lat I've unassigned this. It hadn't been started, so the issue is available again.
jayhemnani9910 commentedon Oct 24, 2026
This looks fixed on main by #1245 (22dfe27).
webhookHandlernow maps only session start/stop (WEBHOOK_EVENT_BY_HOOKinsrc/hook-handlers.tshassessionstart,stop,sessionend), so aPostToolUseno longer becomesskill-use. Theskill-usewebhook is sent fromskillUseHandler, once per skillcreditToolCallreturns, and aReadofREADME.mdcredits none.I haven't re-run the hook simulator from the issue; worth one run of the
README.mdrow before closing.SaulMoro commentedon Oct 25, 2026
Thanks @jayhemnani9910. Confirmed: fixed by #1245 (
22dfe276).We ran the simulator from #1236 through the built CLI on macOS. On
main(13c891e0) and on 0.28.0-beta.8, a CursorReadofREADME.mdsends no webhook, and a CursorReadof aSKILL.mdsends oneskill-usewithskillName. On 0.28.0-beta.7 the sameREADME.mdread sentskill-usewithdata: {}.Only betas were affected. The Cursor
Readmapping that caused it (#1157,7b71316b) first shipped in 0.28.0-beta.5, and 0.28.0-beta.8 has the fix. 0.27.0 (latest) never sent the empty webhook, but it does not count a CursorSKILL.mdread as a skill use either, because its Cursor tool entry matchesSkill, which Cursor never sends. Only/skillprompt mentions are counted there. The next stable release gets both. On a beta, upgrade to 0.28.0-beta.8 or later (npm i -g teamai-cli@0.28.0-beta.8) and runteamai pullonce to replace the old hook entry; no configuration change is needed.Closing as completed. Reopen if a Cursor read of a non-skill file still sends
skill-useon 0.28.0-beta.8 or later.