Skip to content

fix(hooks): read SessionStart source to reset skill dedup - #371

Open
abhiramshaji-cyber wants to merge 1 commit into
vercel:mainfrom
abhiramshaji-cyber:fix/session-start-reset-source
Open

abhiramshaji-cyber wants to merge 1 commit into
vercel:mainfrom
abhiramshaji-cyber:fix/session-start-reset-source

Conversation

@abhiramshaji-cyber

Copy link
Copy Markdown

After /clear or 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.mjs runs on every Claude Code SessionStart (the startup|resume|clear|compact matcher in hooks/hooks.json). Its job is to delete the session's seen-skills and seen-context-chunks files 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 /clear in a Claude Code session that already used the nextjs skill.

  1. Claude Code calls the hook with {"hook_event_name":"SessionStart","source":"clear","session_id":"..."}, the shape in the SessionStart input docs.
  2. main() reads input?.hook_event_name, which is "SessionStart" on every call.
  3. CONTEXT_CLEARING_EVENTS.has("SessionStart") is false, so resetTriggered is false.
  4. resetDedupStateForSession never runs, and vercel-plugin-<session>-seen-skills.d still lists nextjs after 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.

  1. Same payload as above.
  2. main() reads input?.source, which is "clear".
  3. CONTEXT_CLEARING_SOURCES.has("clear") is true and session_id is not empty, so resetTriggered is true.
  4. removeAllSessionDedupArtifacts(sessionId) deletes that session's seen-skills and seen-context-chunks files and folders, including the per agent ones.
  5. On startup or resume, source is 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:#111827
Loading

The change

In hooks/src/session-start-seen-skills.mts, the input type gains source?: string, and the if that decides whether to reset now reads input?.source instead of input?.hook_event_name. The set is renamed CONTEXT_CLEARING_SOURCES and the debug log field is now source. The Cursor branch and the non empty session_id check are unchanged. hooks/session-start-seen-skills.mjs is rebuilt with bun run build:hooks, since the tests run the built file.

In tests/session-start-seen-skills.test.ts, the six fixtures now send hook_event_name: "SessionStart" with the matching source. New cases check that a clear leaves another session's files alone (including its per agent folder), and that nothing is deleted for resume, an unknown or missing source, the old hook_event_name: "clear" and "compact" payloads, a missing or empty session_id, and a Cursor payload. Eight of these fail on main and pass with the change. bun run test (typecheck plus 1288 tests), bun run validate and bun run build:all all 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.mts already deletes by the same vercel-plugin-<session>- prefix on SessionEnd, 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

  • After /clear or a compaction, the plugin starts the session's skill list fresh, so skills can be offered and counted again for the new context.
  • startup and resume keep the list, same as before.
  • Cursor output is unchanged.
  • Only the current session's seen-skills and seen-context-chunks files are removed; other sessions' files stay.

Closes #169

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
@vercel
vercel Bot requested a review from molebox October 29, 2026 12:58

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SessionStart dedup reset reads hook_event_name instead of source; regression fixtures mirror the mismatch

1 participant