Skip to content

fix(extension): handle a rejected sendMessage in loadOpportunityBadge - #6430

Merged
JSONbored merged 1 commit into
JSONbored:mainfrom
dhgoal:fix/extension-badge-sendmessage
Jul 16, 2026
Merged

fix(extension): handle a rejected sendMessage in loadOpportunityBadge#6430
JSONbored merged 1 commit into
JSONbored:mainfrom
dhgoal:fix/extension-badge-sendmessage

Conversation

@dhgoal

@dhgoal dhgoal commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #6189

  • content.js's loadOpportunityBadge awaited chrome.runtime.sendMessage with no try/catch, unlike every other message call site in this extension (background.js:19-21, options.js:78-94, options.js:99-113).
  • sendMessage genuinely rejects under MV3 — service worker asleep/restarting, "Extension context invalidated". Because mountOpportunityBadge calls this as a floating void promise, a rejection became an unhandled rejection and left the badge <aside> in the DOM permanently hidden with zero user-visible feedback.
  • It now cleans up through the same container.remove() the function already uses for its other failure paths — the graceful behaviour the issue asks for.
  • background.js and options.js are untouched; their handling was already correct, as the issue states.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck (this workspace's typecheck is its node --check lint gate — run and passing)
  • npm run test:coverage locally; codecov/patch requires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • Verified the regression test actually catches the bug: with the try/catch reverted (and the test seam left in place, to isolate the fix itself), the new test fails with "promise rejected 'Error: Extension context invalidated' instead of resolving" — precisely the unhandled rejection described — and passes with the fix.
  • This workspace's own gate is green: npx vitest run --coverage passes 22 tests across 4 files, with coverage 100% statements / 100% functions / 100% lines / 95.79% branches, all above vitest.config.ts's thresholds (98/98/98/94). npm run lint (node --check on all five scripts) passes.
  • The measured baseline is unchanged: content.js is deliberately outside coverage.include (the README defers it pending a jsdom mount harness), so this adds real regression coverage without touching the thresholds or the deferral policy.
  • Root-level UI/MCP/worker suites are untouched by an extension-only change; leaving them to CI.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests.
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks.
  • Visible UI changes include a UI Evidence section below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

No UI Evidence section: there is no new rendered state to screenshot. This strictly removes a broken one — on failure the badge container is now cleanly removed instead of persisting as a hidden orphan element, which is the extension's existing behaviour for every other failure path.

Notes

loadOpportunityBadge is added to the existing __loopoverMinerContentInternals test seam so the test can drive it directly. content.js mounts the badge at import time only when location.pathname is a GitHub issue URL, so importing it on a non-issue path loads the module free of DOM side effects — that keeps this focused on the sendMessage failure path and needs none of the jsdom mount harness the README defers.

Closes #6189

loadOpportunityBadge awaited chrome.runtime.sendMessage with no try/catch,
unlike every other message call site in this extension. sendMessage really
does reject under MV3 (service worker asleep/restarting, "Extension context
invalidated"), and mountOpportunityBadge calls this as a floating void
promise -- so a rejection became an unhandled rejection and left the badge
<aside> in the DOM permanently hidden, with no user-visible feedback.

It now cleans up via the same container.remove() the function already uses
for its other failure paths. background.js and options.js are untouched;
their handling was already correct.

Adds test/content.test.ts covering the rejected path plus the not-ok and ok
paths, and exposes loadOpportunityBadge on the existing test-internals seam
so it can be driven directly. content.js mounts at import only on a GitHub
issue pathname, so importing it on a non-issue path stays side-effect-free
and needs none of the deferred jsdom mount harness. content.js remains
outside coverage.include, so the measured baseline is unchanged.

Closes JSONbored#6189
@dhgoal
dhgoal requested a review from JSONbored as a code owner July 16, 2026 07:10
@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label Jul 16, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 16, 2026
@loopover-orb

loopover-orb Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-16 07:41:50 UTC

2 files · 1 AI reviewer · no blockers · CI green · unstable

⏸️ Suggested Action - Manual Review

Review summary
This is a small, correctly-scoped fix wrapping chrome.runtime.sendMessage in try/catch in loadOpportunityBadge, cleaning up the badge container via the existing container.remove() path on rejection instead of leaving an unhandled rejection. It closes the linked issue #6189, matches the error-handling convention already used in background.js and options.js, and ships three focused unit tests covering the reject path, the pre-existing not-ok path, and the success path.

Nits — 4 non-blocking
  • The exported loadOpportunityBadge in content.js:78-83 is now test-only surface (guarded by __LOOPOVER_MINER_EXTENSION_TEST__), so consider a brief comment noting it's exposed solely for testing, consistent with the other exported internals.
  • The catch block swallows the error without logging it (content.js:50-55); consider a console.debug/warn so a genuinely broken extension context isn't silently invisible during manual debugging.
  • Consider also asserting on the error message/logging behavior if you add any diagnostic logging to the catch block in a follow-up.
  • The test file's loadContentInternals helper could be reused/exported if future content.js tests need the same import-and-stub pattern, but that's fine to defer until there's a second consumer.
Flagged checks (non-blocking)
  • Contributor trust — Contributor flagged for review

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #6189
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 113 registered-repo PR(s), 64 merged, 36 issue(s).
Contributor context ✅ Confirmed Gittensor contributor dhgoal; Gittensor profile; 113 PR(s), 36 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Review context
  • Author: dhgoal
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 113 PR(s), 36 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Add a concise scope and risk note.
  • Then work through the remaining 1 step in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot 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.

LoopOver approves — the gate is satisfied and CI is green.

@JSONbored
JSONbored merged commit 6bb11c5 into JSONbored:main Jul 16, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:flagged Contributor flagged for review by trust analysis. gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(extension): content.js's loadOpportunityBadge has no failure handling around chrome.runtime.sendMessage, unlike every other message call site

2 participants