Skip to content

fix(hooks): read Copilot's camelCase subagentStop payload - #1599

Open
dmazhukov wants to merge 2 commits into
rohitg00:mainfrom
dmazhukov:fix/1596-copilot-subagent-stop
Open

dmazhukov wants to merge 2 commits into
rohitg00:mainfrom
dmazhukov:fix/1596-copilot-subagent-stop

Conversation

@dmazhukov

@dmazhukov dmazhukov commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

subagent-stop now reads the camelCase payload Copilot CLI sends for subagentStop: response becomes last_message, agentId becomes agent_id and keeps the event id stable, agentType becomes agent_type. Fixes #1596.

hooks.copilot.json registers subagentStop in camelCase and the hook read only snake_case names, so on Copilot every subagent report was stored empty, and a retry was stored twice.

The reply is now redacted before the 4000-character cut, like stop.ts: a <private> block across the limit kept its secret.

Not covered: Copilot's subagentStart sends no agentId, so start events keep the agent name.
Not covered: field names follow the Copilot CLI hooks reference, not a captured payload.
Not covered: an unclosed <private> tag, or one with attributes, stays unredacted here and on the server.


Devin Review

Summary by CodeRabbit

  • Bug Fixes

    • Improved assistant reply handling across supported payload formats, including camelCase fields and alternate response sources.
    • Private information is now removed from captured assistant replies before they are recorded.
    • Replies are limited to 4,000 characters for more consistent event processing.
    • Improved event stability so repeated events remain consistent while events from different agents remain distinct.
  • Tests

    • Added coverage for payload compatibility, stable event identifiers, privacy filtering, and reply length limits.

The Copilot plugin registers subagentStop in camelCase, so Copilot CLI
sends agentId, agentType and response. The hook read only agent_id,
agent_type and last_assistant_message: the subagent's report was stored
empty, agent_id held the agent name, and without agent_id the event id
fell back to the hook time, so a repeat delivery was stored twice.

The hook now falls back to the camelCase fields, and agentId makes the
event id stable the same way agent_id does.

Signed-off-by: Dmitrii Zhukov <dmitry0983@gmail.com>
stripPrivateData only matches a closed <private> block, and the server
redacts after the hook has already cut the reply, so a block that crossed
the 4000-character limit was stored with its opening tag and the start of
the secret. Copilot's response now reaches the same cut. The hook redacts
the whole reply first, as stop.ts does.

Signed-off-by: Dmitrii Zhukov <dmitry0983@gmail.com>
@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown

@dmazhukov is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 83052429-c8c9-4893-b52e-2dc1687aec8a
📥 Commits

Reviewing files that changed from the base of the PR and between 14dfd6e and 49b6cb1.

📒 Files selected for processing: 3
  • plugin/scripts/subagent-stop.mjs
  • src/hooks/subagent-stop.ts
  • test/copilot-plugin.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

📝 Walkthrough

Both subagent-stop handlers now accept camelCase payload fields and sanitize assistant replies before truncating them. Tests cover payload mapping, stable event IDs, and redaction.

Changes: Subagent-stop payload handling

Layer / File(s) Summary
Normalize and capture subagent-stop payloads
src/hooks/subagent-stop.ts, plugin/scripts/subagent-stop.mjs, test/copilot-plugin.test.ts
Both handlers accept camelCase agent fields and use response when last_assistant_message is absent. They sanitize string replies before truncating them to 4,000 characters and recognize either agent_id or agentId for event stability. Tests cover payload translation, event ID consistency, and redaction.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: rohitg00

Merge Risk: ⚪ Minimal · up to 49b6c

The changed Copilot subagent-stop handling has no identified merge-blocking issue. It is ready to merge after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the main change: supporting Copilot's camelCase subagentStop payload. It is concise and specific.
Linked Issues check Passed Issue #1596 requires camelCase subagentStop fields to map response to last_message, agentId to agent_id, and agentType to agent_type. Both hook implementations add these mappings. Both i…
Out of Scope Changes check Passed The changes are limited to the two subagent-stop implementations and tests for the linked hook behavior. Response redaction before truncation supports the stated hook safety behavior and has a focus…
Full details: Linked Issues check

Explanation

Issue #1596 requires camelCase subagentStop fields to map response to last_message, agentId to agent_id, and agentType to agent_type. Both hook implementations add these mappings. Both implementations mark snake_case and camelCase agent IDs as stable for repeat event IDs. test/copilot-plugin.test.ts verifies the field mapping, stable repeat IDs, and distinct IDs for different agents. The same test verifies the documented response redaction and length behavior.

Full details: Out of Scope Changes check

Explanation

The changes are limited to the two subagent-stop implementations and tests for the linked hook behavior. Response redaction before truncation supports the stated hook safety behavior and has a focused test. No unrelated change is demonstrated.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

{},
{ ...body.data, stop_hook_active: data.stop_hook_active === true },
{ stable: Boolean(data.agent_id) },
{ stable: Boolean(data.agent_id || data.agentId) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Long subagent updates disappear as duplicates

When one Copilot agentId sends distinct replies sharing their first 4,000 characters, withEventId gives both the same ID. capture discards the later report as a duplicate.

Learn more

The hook converts each subagent stop into an observation. For Copilot payloads, agentId makes the event ID stable across repeated delivery, so withEventId hashes the supplied content without a timestamp. That content contains only the first 4,000 characters of the redacted reply. Two legitimate stops from the same agent with a common report prefix therefore have identical IDs, and capture ignores the later one.

Example: A Copilot subagent reports "A".repeat(4000) + "draft", then stops again with "A".repeat(4000) + "finished". Both requests carry the same last_message and event ID, so only the draft observation survives.

Recommended fix: Keep the redacted 4,000-character reply in body.data, but derive the stable event ID using a digest of the full redacted reply as additional content. Identical retries then share an ID while distinct long reports do not.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both stops store the same last_message: the reply is cut to 4000 characters (src/hooks/subagent-stop.ts:35) before it is hashed and stored, so the later event would repeat the first one except for its timestamp. Hashing the full reply would only add that copy. Snake_case payloads with agent_id already behave this way on main: "A".repeat(4000) + "draft" and + "finished" give one event id and an identical last_message there and on this head.

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.

Copilot subagentStop drops the subagent's response and ignores agentId

1 participant