Skip to content

feat: surface external plugin review signals - #2623

Merged
aaronpowell merged 4 commits into
mainfrom
aaronpowell-expand-plugin-intake-signals
Aug 12, 2026
Merged

feat: surface external plugin review signals#2623
aaronpowell merged 4 commits into
mainfrom
aaronpowell-expand-plugin-intake-signals

Conversation

@aaronpowell

Copy link
Copy Markdown
Contributor

Pull Request Checklist

  • I have read and followed the CONTRIBUTING.md guidelines.
  • I have read and followed the Guidance for submissions involving paid services.
  • My contribution adds a new instruction, prompt, agent, skill, workflow, or canvas extension file in the correct directory.
  • The file follows the required naming convention.
  • The content is clearly structured and follows the example format.
  • I have tested my instructions, prompt, agent, skill, or canvas extension with GitHub Copilot.
  • I have run npm start and verified that README.md is up to date.
  • I am targeting the main branch for this pull request.

Description

External plugin intake currently validates repository structure and metadata, but gives maintainers little visibility into signals such as a newly created repository with no community activity or a homepage that is primarily marketing a paid service. This change surfaces those signals without attempting to decide whether a plugin is genuinely awesome.

The intake now records repository age, stars, watchers, forks, and open issues, and performs a bounded, timed homepage inspection for pricing, sales, trial, and checkout language. These results appear in a clearly labeled, non-blocking Reviewer signals section. Approval and rejection commands now both react with eyes to acknowledge the maintainer decision consistently.


Type of Contribution

  • New instruction file.
  • New prompt file.
  • New agent file.
  • New plugin.
  • New skill file.
  • New agentic workflow.
  • New canvas extension.
  • Update to existing instruction, prompt, agent, plugin, skill, workflow, or canvas extension.
  • Other (please specify):

Additional Notes

The website check is intentionally deterministic and non-blocking rather than agentic: this keeps it reproducible, auditable, bounded, and less exposed to prompt injection from submitted webpages. Focused intake and quality-gate tests pass.


By submitting this pull request, I confirm that my contribution abides by the Code of Conduct and will be licensed under the MIT License.

Add repository and homepage heuristics to external plugin intake and use eyes reactions for approval decisions.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: b745e915-6c5a-4354-ab77-5b52f9e66fea
Copilot AI balanced review requested due to automatic review settings August 11, 2026 03:41
@github-actions github-actions Bot added the workflow PR touches workflow automation label Aug 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds non-blocking repository and homepage signals to external plugin intake for maintainer review.

Changes:

  • Collects repository age and activity metrics.
  • Inspects homepage content for commercial signals.
  • Standardizes approval and rejection reactions.
Show a summary per file
File Description
eng/external-plugin-intake.mjs Implements and displays reviewer signals.
eng/external-plugin-intake.test.mjs Tests repository and homepage signals.
.github/workflows/external-plugin-command-router.yml Uses eyes reactions for decisions.

Review details

Tip

Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 3/3 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread eng/external-plugin-intake.mjs Outdated
Comment thread eng/external-plugin-intake.mjs Outdated
Comment thread eng/external-plugin-intake.mjs
Comment thread eng/external-plugin-intake.mjs Outdated
Validate homepage destinations and bound response reads, and correct repository activity metrics.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: b745e915-6c5a-4354-ab77-5b52f9e66fea
Copilot AI review requested due to automatic review settings August 11, 2026 04:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (1)

eng/external-plugin-intake.mjs:372

  • When chunk boundaries make totalBytes reach exactly 512 KB, the loop exits without calling reader.cancel(). Releasing the lock does not cancel the response, and the timeout is then cleared, so an untrusted server can leave the remaining body/socket active indefinitely. Cancel whenever the cap is reached, not only when one chunk crosses it.
    while (totalBytes < HOMEPAGE_MAX_BYTES) {
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread eng/external-plugin-intake.mjs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b745e915-6c5a-4354-ab77-5b52f9e66fea
Copilot AI review requested due to automatic review settings August 11, 2026 04:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (4)

eng/external-plugin-intake.mjs:417

  • If chunks total exactly 512,000 bytes while the server keeps the stream open, the loop exits via its condition without calling cancel(). The lock is then released and the inspection timeout is cleared, leaving the untrusted response/socket active indefinitely. Cancel whenever the byte ceiling is reached, not only when one chunk is truncated.
    while (totalBytes < HOMEPAGE_MAX_BYTES) {

eng/external-plugin-intake.mjs:475

  • Redirect response bodies are neither consumed nor canceled before the next request. A submitted server can return several redirects with never-ending bodies; after the final response completes, the timeout is cleared while those earlier sockets remain active and can keep the workflow alive. Cancel each redirect body before following its Location.
      if (response.status < 300 || response.status >= 400) break;
      const location = response.headers?.get?.("location");
      if (!location) break;
      if (redirectCount === HOMEPAGE_MAX_REDIRECTS) {
        return { ...result, status: "warning", output: "Homepage exceeded the redirect limit." };

eng/external-plugin-intake.mjs:397

  • This does not implement the dispatcher handler's flow-control contract. onHeaders/onData may return false to pause delivery and later invoke the supplied resume callback, but the no-op callback and unconditional data handling keep the socket flowing. A large untrusted response can consequently be queued by fetch before the 512 KB reader cancels it, defeating the intended memory bound.

This issue also appears in the following locations of the same file:

  • line 417
  • line 471
      if (handler.onHeaders(response.statusCode, headers, () => {}, response.statusMessage) === false) {
        request.destroy();
        return;
      }
      response.on("data", (chunk) => handler.onData(chunk));

eng/external-plugin-intake.mjs:464

  • The advertised 10-second inspection bound does not cover DNS resolution: lookup() has no connection to the AbortController, so a slow or unresponsive authoritative DNS path can keep this await pending after the timer fires. Apply an enforceable timeout to address resolution as well; otherwise issue opens/edits can occupy workflow runs longer than the configured limit.
      const address = await assertPublicUrl(url);
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread eng/external-plugin-intake.mjs
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 12, 2026 00:06
@aaronpowell
aaronpowell merged commit 280b05d into main Aug 12, 2026
15 checks passed
@aaronpowell
aaronpowell deleted the aaronpowell-expand-plugin-intake-signals branch August 12, 2026 00:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (4)

eng/external-plugin-intake.mjs:433

  • If received chunks total exactly 512,000 bytes while more data remains, the loop condition becomes false without taking the truncation branch, so the reader is released but never canceled. That leaves the response/socket alive after the timeout is cleared. Cancel whenever the byte ceiling is reached, including this exact-boundary case.
    return chunks.join("") + decoder.decode();

eng/external-plugin-intake.mjs:479

  • Redirect responses are followed without consuming or canceling their bodies. A submitted server can return a redirect with a never-ending body, leaving each prior socket active even after the final inspection clears its timeout. Cancel every redirect response before following or returning from it.
      if (response.status < 300 || response.status >= 400) break;
      const location = response.headers?.get?.("location");
      if (!location) break;
      if (redirectCount === HOMEPAGE_MAX_REDIRECTS) {
        return { ...result, status: "warning", output: "Homepage exceeded the redirect limit." };

eng/external-plugin-intake.mjs:487

  • A non-success final response is also returned without canceling its body. Since the timeout is cleared in finally, a 4xx/5xx endpoint with an unfinished body can leave the Actions process holding an active request indefinitely. Cancel the body before returning the warning.
    if (!response.ok) {
      return { ...result, status: "warning", output: `Homepage returned HTTP ${response.status}.` };

eng/external-plugin-intake.mjs:401

  • The custom dispatcher does not honor response backpressure: the empty resume callback cannot restart a paused consumer, and handler.onData's return value is discarded while the Node response remains in flowing mode. An untrusted homepage can therefore be buffered beyond the advertised 512 KB limit. Pause the source when the handler returns false and supply a working resume callback.

This issue also appears in the following locations of the same file:

  • line 433
  • line 475
  • line 486
      if (handler.onHeaders(response.statusCode, headers, () => {}, response.statusMessage) === false) {
        request.destroy();
        return;
      }
      response.on("data", (chunk) => handler.onData(chunk));
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

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

Labels

workflow PR touches workflow automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants