Repository navigation
fix(hooks): read SessionStart source to reset skill dedup - #371
Open
abhiramshaji-cyber wants to merge 1 commit into
Open
abhiramshaji-cyber wants to merge 1 commit into
abhiramshaji-cyber wants to merge 1 commit into
Conversation
Claude Code sends hook_event_name "SessionStart" on every SessionStart call and puts the trigger (startup, resume, clear, compact) in the source field. The reset check compared hook_event_name with clear and compact, so it never matched and dedup state survived /clear and compaction. The test fixtures put the trigger in hook_event_name too, which hid the mismatch. Reading source makes the reset run when the context is actually cleared. The fixtures now send the documented payload, and new cases cover resume, unknown and missing source, legacy payloads, missing session ids, the Cursor branch, and that another session's files are left alone. Refs vercel#169
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
After
/clearor a compaction, the plugin never forgot which skills it had already recorded for the session; this makes it start fresh at those moments, as intended.What this code is for
hooks/session-start-seen-skills.mjsruns on every Claude CodeSessionStart(thestartup|resume|clear|compactmatcher inhooks/hooks.json). Its job is to delete the session'sseen-skillsandseen-context-chunksfiles in the temp folder when the context window has just been wiped, so the plugin does not treat skills from the old context as still present. On Cursor it prints{ env: { VERCEL_PLUGIN_SEEN_SKILLS: "" } }and stops.Before this PR
A developer runs
/clearin a Claude Code session that already used thenextjsskill.{"hook_event_name":"SessionStart","source":"clear","session_id":"..."}, the shape in the SessionStart input docs.main()readsinput?.hook_event_name, which is"SessionStart"on every call.CONTEXT_CLEARING_EVENTS.has("SessionStart")is false, soresetTriggeredis false.resetDedupStateForSessionnever runs, andvercel-plugin-<session>-seen-skills.dstill listsnextjsafter the clear.The tests passed anyway because the six fixtures sent
hook_event_name: "clear"(and"compact","startup"), a payload Claude Code never sends.After this PR
Same developer, same
/clear.main()readsinput?.source, which is"clear".CONTEXT_CLEARING_SOURCES.has("clear")is true andsession_idis not empty, soresetTriggeredis true.removeAllSessionDedupArtifacts(sessionId)deletes that session'sseen-skillsandseen-context-chunksfiles and folders, including the per agent ones.startuporresume,sourceis not in the set, so nothing is deleted.The diagram
flowchart LR classDef box fill:#eef1ff,stroke:#4c5bd4,color:#111827 classDef bad fill:#ffd6d6,stroke:#cc0000,color:#111827,stroke-width:3px classDef good fill:#c8f0c8,stroke:#008800,color:#111827,stroke-width:3px subgraph BEFORE["BEFORE: 0 resets after /clear or compact"] direction TB B1["developer runs /clear"] --> B2["SessionStart payload, source clear"] B2 --> B3["main() reads hook_event_name"] B3 --> B4{"value is always SessionStart"} B4 -->|"never clear or compact"| B5["seen-skills files kept"] B4 -->|"startup or resume"| B6["seen-skills files kept"] end subgraph AFTER["AFTER: 1 reset per /clear or compact"] direction TB A1["developer runs /clear"] --> A2["SessionStart payload, source clear"] A2 --> A3["main() reads source"] A3 --> A4{"THE CHANGE: source is clear or compact"} A4 -->|"clear or compact"| A5["this session's files removed"] A4 -->|"startup or resume"| A6["seen-skills files kept, as before"] end B1 ~~~ A1 class B1,B2,B3,B6 box class A1,A2,A3,A6 box class B4,B5 bad class A4,A5 good linkStyle 3 stroke:#cc0000,stroke-width:5px linkStyle 8 stroke:#008800,stroke-width:5px style BEFORE fill:#fff5f5,stroke:#cc0000,color:#111827 style AFTER fill:#f3fff3,stroke:#008800,color:#111827The change
In
hooks/src/session-start-seen-skills.mts, the input type gainssource?: string, and theifthat decides whether to reset now readsinput?.sourceinstead ofinput?.hook_event_name. The set is renamedCONTEXT_CLEARING_SOURCESand the debug log field is nowsource. The Cursor branch and the non emptysession_idcheck are unchanged.hooks/session-start-seen-skills.mjsis rebuilt withbun run build:hooks, since the tests run the built file.In
tests/session-start-seen-skills.test.ts, the six fixtures now sendhook_event_name: "SessionStart"with the matchingsource. New cases check that a clear leaves another session's files alone (including its per agent folder), and that nothing is deleted forresume, an unknown or missingsource, the oldhook_event_name: "clear"and"compact"payloads, a missing or emptysession_id, and a Cursor payload. Eight of these fail onmainand pass with the change.bun run test(typecheck plus 1288 tests),bun run validateandbun run build:allall pass.This follows the plan @yuu-sugiyama laid out in the issue almost line for line, so the credit for finding it and for the list of cases goes to them. On their follow up about session ids that share a prefix with a scoped name:
session-end-cleanup.mtsalready deletes by the samevercel-plugin-<session>-prefix onSessionEnd, and Claude Code session ids are UUIDs, so a different UUID cannot start with<uuid>-. The new test uses two UUIDs to check exactly that. Changing the file naming so the session and scope can never run together felt like a separate decision that belongs to you, so I left it out. Would you prefer that land first, or would you rather drop this reset path entirely? Happy to go either way.What changes for a user
/clearor a compaction, the plugin starts the session's skill list fresh, so skills can be offered and counted again for the new context.startupandresumekeep the list, same as before.seen-skillsandseen-context-chunksfiles are removed; other sessions' files stay.Closes #169