Skip to content

fix: call sidePanel.open() synchronously to fix context menu side panel#963

Open
octo-patch wants to merge 6 commits intoChatGPTBox-dev:masterfrom
octo-patch:fix/issue-857-side-panel-context-menu
Open

fix: call sidePanel.open() synchronously to fix context menu side panel#963
octo-patch wants to merge 6 commits intoChatGPTBox-dev:masterfrom
octo-patch:fix/issue-857-side-panel-context-menu

Conversation

@octo-patch
Copy link
Copy Markdown

@octo-patch octo-patch commented Apr 20, 2026

Fixes #857

Problem

Clicking "Open Side Panel" from the right-click context menu does nothing, with this error in the background script console:

Uncaught (in promise) Error: sidePanel.open() may only be called in response to a user gesture.

Root cause: In menus.mjs, the onClickMenu handler immediately kicks off an async Browser.tabs.query() call. The sidePanel.open() is then called inside the .then() callback — but by that point, Chrome no longer considers it to be within the original user gesture context, so the API call is rejected.

Solution

Extract the itemId before the async query, and handle the openSidePanel action synchronously at the top of the event handler — before any async operations — so that sidePanel.open() is called within the original user gesture chain.

All other menu actions that require the current tab information are unaffected and still go through the Browser.tabs.query() path.

Testing

  • Right-click on a page → ChatGPTBox → Open Side Panel → side panel now opens correctly
  • Other context menu actions (selection tools, close chats) continue to work as before

Open in Devin Review

Summary by CodeRabbit

  • New Features

    • Broader support for Claude API session key formats for improved credential compatibility.
  • Improvements

    • Faster, more direct handling when opening the side panel from menus and commands.
    • Clearer side-panel behavior based on trigger context to avoid unnecessary work.
  • Bug Fixes

    • Added synchronous guards and rejection handlers to prevent unhandled promise rejections and surface errors when side-panel open is unsupported.

octo-patch added 2 commits April 20, 2026 09:43
…Box-dev#935)

Anthropic now issues session keys with the prefix sk-ant-sid02-
in addition to the previous sk-ant-sid01- format. The validation
was too strict, rejecting valid new-format keys with an error.

Relaxed the check to match any sk-ant-sid prefix to be
forward-compatible with future key format versions.
…el (fixes ChatGPTBox-dev#857)

Chrome's sidePanel.open() must be called synchronously within a user
gesture handler. Previously, the call was inside a Browser.tabs.query()
Promise callback, which breaks the user gesture chain and causes:

  Error: sidePanel.open() may only be called in response to a user gesture.

Fixed by extracting the itemId before the async query and handling the
openSidePanel action synchronously at the top of the event handler,
before any async operations.
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Fix side panel context menu and Claude session key validation

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Fix sidePanel.open() call to execute synchronously within user gesture context
• Relax Claude session key validation to accept sk-ant-sid02 format
• Extract menuItemId before async operations to preserve gesture chain
• Update error message to reflect both session key formats
Diagram
flowchart LR
  A["User clicks context menu"] -->|synchronously| B["Check if openSidePanel action"]
  B -->|yes| C["Call sidePanel.open immediately"]
  C --> D["Return before async query"]
  B -->|no| E["Extract itemId for async path"]
  E --> F["Query active tab asynchronously"]
  F --> G["Handle other menu actions"]
  H["Claude session key validation"] -->|sk-ant-sid01 or 02| I["Accept key"]
  H -->|other prefix| J["Reject key"]
Loading

Grey Divider

File Changes

1. src/background/menus.mjs 🐞 Bug fix +12/-1

Call sidePanel.open synchronously in menu handler

• Extract itemId before async operations to preserve user gesture context
• Handle openSidePanel action synchronously at top of handler before any async calls
• Call menuConfig.openSidePanel.action() directly within gesture chain
• Reuse extracted itemId in async path to avoid redundant computation

src/background/menus.mjs


2. src/services/clients/claude/index.mjs 🐞 Bug fix +2/-2

Accept Claude session keys in sk-ant-sid02 format

• Relax session key validation from strict sk-ant-sid01 prefix to generic sk-ant-sid prefix
• Support new Anthropic session key format sk-ant-sid02-*****
• Update error message to document both accepted key formats
• Enable forward compatibility with future key format versions

src/services/clients/claude/index.mjs


Grey Divider

Qodo Logo

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09e7e50c-b518-4ade-a9c3-17f0c23d3085

📥 Commits

Reviewing files that changed from the base of the PR and between 0f82ecd and 025cbb8.

📒 Files selected for processing (1)
  • src/background/menus.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/background/menus.mjs

📝 Walkthrough

Walkthrough

Background menu handler now computes itemId once and adds a synchronous early-return path to call menuConfig.openSidePanel.action(true, tab) directly. Content-script openSidePanel.action became synchronous with controlled Promise returns. Claude session-key validation was relaxed. Command actions now guard synchronous exceptions and promise rejections.

Changes

Cohort / File(s) Summary
Side panel: background + content-script
src/background/menus.mjs, src/content-script/menu-tools/index.mjs
Derive itemId up front; add synchronous early-return for openSidePanel to call menuConfig.openSidePanel.action(true, tab) immediately (wrap in try/catch and attach .catch if Promise-like). Change openSidePanel.action from async to synchronous (fromBackground, tab) => { ... }; when invoked from background it returns chrome.sidePanel.open(...) or Promise.reject(...) if unavailable; when called locally it may return undefined.
Background command handling
src/background/commands.mjs
Wrap command action invocation in try/catch; capture result and attach .catch when return value appears Promise-like to avoid unhandled rejections.
Claude client validation
src/services/clients/claude/index.mjs
Relax session-key validation to accept prefixes starting with sk-ant-sid; update error message to allow sk-ant-sid01-***** or sk-ant-sid02-*****.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant BG as Background
  participant CS as ContentScript
  participant SP as ChromeSidePanel

  User->>BG: right-click → select "Open Side Panel" (user gesture)
  BG->>BG: compute itemId from info.menuItemId
  alt itemId == openSidePanel and menuConfig.openSidePanel.action exists
    BG->>CS: call menuConfig.openSidePanel.action(true, tab)
    CS->>SP: chrome.sidePanel.open(...)  
    SP-->>CS: success / error
    CS-->>BG: promise resolved/rejected (if applicable)
  else
    BG->>BG: Browser.tabs.query(...).then(...) or sendMessage path
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Review effort 4/5

Poem

🐰 I hopped from menu to code,
I caught the click where gestures flowed.
A quick return, the panel sighed,
No hungry promise left untried.
Keys widened — onward we go!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Claude session-key validation change in src/services/clients/claude/index.mjs is out of scope and unrelated to fixing the context menu side panel issue. Remove the Claude session-key changes or move them to a separate PR; they should be tracked in issue #962 as mentioned in comments.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: calling sidePanel.open() synchronously to fix the context menu side panel issue.
Linked Issues check ✅ Passed All code changes directly address the linked issue #857 requirements: synchronous sidePanel.open() invocation within user-gesture context and error handling for edge cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Apr 20, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Claude key validation too loose 🐞 Bug ≡ Correctness
Description
The constructor now only checks sessionKey.startsWith('sk-ant-sid') while the error message claims
only sk-ant-sid01-***** or sk-ant-sid02-***** are valid, allowing malformed keys to pass
validation and fail later during authenticated requests.
Code

src/services/clients/claude/index.mjs[R77-79]

+    if (!sessionKey.startsWith('sk-ant-sid')) {
+      throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
}
Evidence
The new prefix check accepts values like sk-ant-sid (no version/dash) or other unintended
variants, but the client later uses the value verbatim in the cookie: sessionKey=... header for
API calls. This turns what should be an immediate validation error into harder-to-debug downstream
API failures.

src/services/clients/claude/index.mjs[74-84]
src/services/clients/claude/index.mjs[218-224]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Claude `sessionKey` validation was loosened to a generic `startsWith('sk-ant-sid')`, which no longer enforces the documented `sid01`/`sid02` formats and allows malformed keys through.
### Issue Context
The thrown error message still claims the key must be `sk-ant-sid01-***** or sk-ant-sid02-*****`, but the code does not enforce that constraint.
### Fix Focus Areas
- src/services/clients/claude/index.mjs[74-84]
### Suggested fix
- Replace the `startsWith('sk-ant-sid')` check with a stricter pattern that matches the message, e.g.:
- `/^sk-ant-sid0[12]-/`
- Alternatively, if you intended to allow any `sidXX`, update both validation and error message consistently (e.g., `/^sk-ant-sid\d{2}-/`).
- Ensure the error message and validation logic match exactly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Side panel open can throw🐞 Bug ☼ Reliability
Description
onClickMenu now calls menuConfig.openSidePanel.action(true, tab) synchronously and returns, but
the action unconditionally dereferences tab.windowId/tab.id and calls chrome.sidePanel.open()
without feature detection or error handling, so a missing API (or unexpected tab shape) will throw
directly in the click handler.
Code

src/background/menus.mjs[R14-17]

+  if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) {
+    menuConfig.openSidePanel.action(true, tab)
+    return
+  }
Evidence
The new synchronous path invokes openSidePanel.action immediately. That action calls
chrome.sidePanel.open({ windowId: tab.windowId, tabId: tab.id }) with no guard/try-catch, while
other side panel code in the background script explicitly detects SidePanel API availability and
falls back with warnings instead of throwing.

src/content-script/menu-tools/index.mjs[60-70]
src/background/index.mjs[930-975]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The synchronous `openSidePanel` path can throw (e.g., SidePanel API not available, or `tab` missing expected fields), which will surface as an unhandled exception during the context-menu click handler.
### Issue Context
`onClickMenu` now returns early after calling `menuConfig.openSidePanel.action(true, tab)`. The underlying action uses `chrome.sidePanel.open({ windowId: tab.windowId, tabId: tab.id })` without feature detection/try-catch.
### Fix Focus Areas
- src/background/menus.mjs[14-17]
- src/content-script/menu-tools/index.mjs[60-70]
### Suggested fix
- Keep the `sidePanel.open()` call synchronous (no `await` before it), but add:
- `tab` shape guard (`tab?.id` / `tab?.windowId`)
- API availability check (`Browser.sidePanel?.open` or `globalThis.chrome?.sidePanel?.open`)
- `try/catch` around the `open()` call with a clear `console.warn` so failures don’t crash the handler.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the menu click handler to ensure sidePanel.open() is called synchronously within a user gesture and updates the Claude session key validation to support multiple versions. Feedback suggests adding error handling for the asynchronous menu action to prevent unhandled promise rejections and making the Claude session key error message more generic to match the updated validation logic.

Comment thread src/background/menus.mjs Outdated
// Chrome's user gesture requirement and causes the error:
// "sidePanel.open() may only be called in response to a user gesture."
if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) {
menuConfig.openSidePanel.action(true, tab)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The action function is asynchronous and returns a Promise. Since it is called here without await or a .catch() block, any errors occurring within the action (such as a failure in the chrome.sidePanel.open API call) will result in an unhandled promise rejection in the background script. It is recommended to add a .catch() block to handle potential errors gracefully.

Suggested change
menuConfig.openSidePanel.action(true, tab)
menuConfig.openSidePanel.action(true, tab).catch(console.error)

Comment on lines +77 to +78
if (!sessionKey.startsWith('sk-ant-sid')) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The validation check and the error message are inconsistent. The check now allows any session key starting with sk-ant-sid (including potential future versions like sid03), but the error message explicitly mentions only sid01 and sid02. To be more future-proof and consistent with the code logic, the error message should be updated to a more generic format.

Suggested change
if (!sessionKey.startsWith('sk-ant-sid')) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
if (!sessionKey.startsWith('sk-ant-sid')) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid-*****')
}

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

Comment on lines +77 to 79
if (!sessionKey.startsWith('sk-ant-sid')) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
}
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.

Action required

1. Claude key validation too loose 🐞 Bug ≡ Correctness

The constructor now only checks sessionKey.startsWith('sk-ant-sid') while the error message claims
only sk-ant-sid01-***** or sk-ant-sid02-***** are valid, allowing malformed keys to pass
validation and fail later during authenticated requests.
Agent Prompt
### Issue description
Claude `sessionKey` validation was loosened to a generic `startsWith('sk-ant-sid')`, which no longer enforces the documented `sid01`/`sid02` formats and allows malformed keys through.

### Issue Context
The thrown error message still claims the key must be `sk-ant-sid01-***** or sk-ant-sid02-*****`, but the code does not enforce that constraint.

### Fix Focus Areas
- src/services/clients/claude/index.mjs[74-84]

### Suggested fix
- Replace the `startsWith('sk-ant-sid')` check with a stricter pattern that matches the message, e.g.:
  - `/^sk-ant-sid0[12]-/`
- Alternatively, if you intended to allow any `sidXX`, update both validation and error message consistently (e.g., `/^sk-ant-sid\d{2}-/`).
- Ensure the error message and validation logic match exactly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/background/menus.mjs`:
- Around line 14-16: The call to menuConfig.openSidePanel.action(true, tab) is
intentionally synchronous to preserve user-gesture requirements but currently
can yield unhandled promise rejections because the action (defined in
src/content-script/menu-tools/index.mjs) is async and its internal
chrome.sidePanel.open Promise isn't returned; fix by updating the content-script
helper at src/content-script/menu-tools/index.mjs (the function that calls
chrome.sidePanel.open around line 66) to return the Promise from
chrome.sidePanel.open, and in src/background/menus.mjs where you handle itemId
=== 'openSidePanel' call menuConfig.openSidePanel.action(true, tab) and, if it
returns a Promise, attach a .catch to handle/log failures (e.g., check for a
returned value and call .catch on it) so you keep the synchronous call site
while observing any async errors.

In `@src/services/clients/claude/index.mjs`:
- Around line 77-78: The current sessionKey check using
sessionKey.startsWith('sk-ant-sid') accepts any variant but the error message
only mentions sk-ant-sid01-***** and sk-ant-sid02-*****; update the validation
in the constructor where sessionKey is checked to either (A) enforce the
documented formats by replacing the startsWith check with a regex that matches
exactly sk-ant-sid01-... or sk-ant-sid02-... (e.g., /^sk-ant-sid0[12]-\S+$/) so
malformed keys are rejected early, or (B) if you intend to accept all
sk-ant-sid* variants, change the Error message to refer generically to
sk-ant-sid* formats; modify the check around the sessionKey variable accordingly
(the same location as the existing sessionKey.startsWith call).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c525545-8a53-47ae-9f16-165c4f3f71be

📥 Commits

Reviewing files that changed from the base of the PR and between c236a4b and 792b6bc.

📒 Files selected for processing (2)
  • src/background/menus.mjs
  • src/services/clients/claude/index.mjs

Comment thread src/background/menus.mjs
Comment on lines +14 to +16
if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) {
menuConfig.openSidePanel.action(true, tab)
return
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect side-panel action implementations and call sites.
# Expect: openSidePanel.action returns/awaits/catches chrome.sidePanel.open(...),
# and menus.mjs observes the action promise with .catch(...) or equivalent.

rg -n -C5 "openSidePanel|chrome\.sidePanel\.open|menuConfig\.openSidePanel\.action" --iglob "*.mjs"

Repository: ChatGPTBox-dev/chatGPTBox

Length of output: 2114


Handle side-panel action failures without moving the API call out of the gesture.

Line 15 intentionally calls the action synchronously, which preserves the user-gesture requirement. However, src/content-script/menu-tools/index.mjs:62-70 defines this action as async but does not return the Promise from chrome.sidePanel.open(...), and menus.mjs does not observe the returned Promise. This allows unhandled promise rejections if sidePanel.open() fails. Keep the call synchronous, but handle failures by observing the returned Promise:

   if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) {
-    menuConfig.openSidePanel.action(true, tab)
+    void Promise.resolve(menuConfig.openSidePanel.action(true, tab)).catch((error) => {
+      console.error('failed to open side panel', error)
+    })
     return
   }

Also update src/content-script/menu-tools/index.mjs:66 to return the Promise:

-        chrome.sidePanel.open({ windowId: tab.windowId, tabId: tab.id })
+        return chrome.sidePanel.open({ windowId: tab.windowId, tabId: tab.id })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) {
menuConfig.openSidePanel.action(true, tab)
return
if (itemId === 'openSidePanel' && menuConfig.openSidePanel?.action) {
void Promise.resolve(menuConfig.openSidePanel.action(true, tab)).catch((error) => {
console.error('failed to open side panel', error)
})
return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/background/menus.mjs` around lines 14 - 16, The call to
menuConfig.openSidePanel.action(true, tab) is intentionally synchronous to
preserve user-gesture requirements but currently can yield unhandled promise
rejections because the action (defined in
src/content-script/menu-tools/index.mjs) is async and its internal
chrome.sidePanel.open Promise isn't returned; fix by updating the content-script
helper at src/content-script/menu-tools/index.mjs (the function that calls
chrome.sidePanel.open around line 66) to return the Promise from
chrome.sidePanel.open, and in src/background/menus.mjs where you handle itemId
=== 'openSidePanel' call menuConfig.openSidePanel.action(true, tab) and, if it
returns a Promise, attach a .catch to handle/log failures (e.g., check for a
returned value and call .catch on it) so you keep the synchronous call site
while observing any async errors.

Comment on lines +77 to +78
if (!sessionKey.startsWith('sk-ant-sid')) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
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.

⚠️ Potential issue | 🟡 Minor

Align the validation with the stated accepted formats.

Line 77 accepts any sk-ant-sid* value, but Line 78 documents only sk-ant-sid01-***** and sk-ant-sid02-*****. This lets malformed keys pass constructor validation and fail later during requests.

Proposed fix
-    if (!sessionKey.startsWith('sk-ant-sid')) {
+    const isValidSessionKey = ['sk-ant-sid01-', 'sk-ant-sid02-'].some((prefix) =>
+      sessionKey.startsWith(prefix),
+    )
+    if (!isValidSessionKey) {
       throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
     }

If accepting all future sk-ant-sid* variants is intentional, update the error message instead.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!sessionKey.startsWith('sk-ant-sid')) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
const isValidSessionKey = ['sk-ant-sid01-', 'sk-ant-sid02-'].some((prefix) =>
sessionKey.startsWith(prefix),
)
if (!isValidSessionKey) {
throw new Error('Session key invalid: Must be in the format sk-ant-sid01-***** or sk-ant-sid02-*****')
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/clients/claude/index.mjs` around lines 77 - 78, The current
sessionKey check using sessionKey.startsWith('sk-ant-sid') accepts any variant
but the error message only mentions sk-ant-sid01-***** and sk-ant-sid02-*****;
update the validation in the constructor where sessionKey is checked to either
(A) enforce the documented formats by replacing the startsWith check with a
regex that matches exactly sk-ant-sid01-... or sk-ant-sid02-... (e.g.,
/^sk-ant-sid0[12]-\S+$/) so malformed keys are rejected early, or (B) if you
intend to accept all sk-ant-sid* variants, change the Error message to refer
generically to sk-ant-sid* formats; modify the check around the sessionKey
variable accordingly (the same location as the existing sessionKey.startsWith
call).

The synchronous menuConfig.openSidePanel.action(true, tab) call is
intentional — chrome.sidePanel.open() must run inside the user-gesture
callback or it throws 'sidePanel.open() may only be called in response
to a user gesture'. We don't want to break that.

But the action used to be async, so the chrome.sidePanel.open Promise
was discarded and any rejection (e.g. an invalid tab/window combo)
would surface as an unhandled rejection in the background script.

Two small changes:
- menu-tools/index.mjs: drop the async wrapper and return the
  chrome.sidePanel.open() promise directly so the caller can observe it.
- background/menus.mjs: still call the action synchronously (gesture
  preserved) but, if it returned a thenable, attach a .catch that logs
  the failure instead of letting it become unhandled.

Addresses review feedback from CodeRabbit and gemini-code-assist.
@octo-patch
Copy link
Copy Markdown
Author

Thanks for the reviews! I've pushed a follow-up commit (327c967) that addresses the unhandled-rejection concern raised by CodeRabbit and gemini-code-assist on src/background/menus.mjs:15.

Changes:

  • src/content-script/menu-tools/index.mjs: drop the unnecessary async wrapper and return the chrome.sidePanel.open() promise so the caller can observe it.
  • src/background/menus.mjs: still call the action synchronously (preserves the user-gesture requirement), but if a thenable is returned, attach a .catch that logs the failure instead of letting it become an unhandled rejection.

The Claude session-key validation comment isn't in scope for this PR — it was the change introduced by #962 and is being tracked/addressed there.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

⚠️ 1 issue in files not directly in the diff

⚠️ Unhandled promise rejection from chrome.sidePanel.open() in keyboard command handler (src/background/commands.mjs:15)

The PR explicitly fixed the unhandled rejection from chrome.sidePanel.open() in src/background/menus.mjs:19-21 by attaching a .catch() to the returned Promise. However, the same openSidePanel action is also invoked from src/background/commands.mjs:15 (via keyboard shortcut), where the returned Promise is silently discarded. Since the action was changed from async (which swallowed the inner promise) to a plain function that directly returns the chrome.sidePanel.open() Promise (src/content-script/menu-tools/index.mjs:66), a failure (e.g., side panel API not available, or invalid tab/window) will surface as an unhandled promise rejection in the background service worker, which could cause it to be terminated by Chrome.

View 5 additional findings in Devin Review.

Open in Devin Review

coderabbitai[bot]

This comment was marked as resolved.

… against missing chrome.sidePanel

Addresses two follow-up review findings on PR ChatGPTBox-dev#963:

1. devin-ai-integration[bot]: src/background/commands.mjs:15 had the
   same unhandled-rejection pattern as menus.mjs. The keyboard command
   path now mirrors the menus.mjs treatment: call the action
   synchronously to preserve user-gesture context, then attach a
   .catch() to the returned thenable so a rejected sidePanel.open()
   does not bubble up as an unhandled rejection.

2. coderabbitai[bot]: src/content-script/menu-tools/index.mjs:62-69
   called chrome.sidePanel.open directly, which throws synchronously
   in browsers where chrome.sidePanel is not defined (e.g. Firefox).
   Guard the call with a typeof chrome / chrome.sidePanel check and
   return a rejected Promise so the caller's .catch() handles it
   uniformly with API rejections.
@octo-patch
Copy link
Copy Markdown
Author

Thanks for the additional findings, @devin-ai-integration and @coderabbitai! I've pushed a follow-up commit (810e8d3) that addresses both:

1. src/background/commands.mjs:15 — unhandled-rejection in the keyboard-command handler (devin-ai-integration)

The keyboard-command path had the same pattern as menus.mjs before commit 327c967: it invoked menuConfig[command].action(true, tab) without observing the returned Promise. If a user binds a shortcut to openSidePanel, the rejected chrome.sidePanel.open() Promise would have become an unhandled rejection in the background script.

The fix mirrors the menus.mjs treatment — keep the call synchronous (preserves the user-gesture requirement), but attach a .catch() to the returned thenable.

2. src/content-script/menu-tools/index.mjs:62-69 — guard against missing chrome.sidePanel (coderabbitai)

chrome.sidePanel.open throws synchronously in browsers where chrome.sidePanel is not defined (e.g. Firefox loading the same bundle). The action handler now checks typeof chrome === 'undefined' and chrome.sidePanel?.open first, returning a rejected Promise when the API is missing. This keeps error handling uniform with actual API rejections — both flow through the caller's .catch().

The Claude session-key validation comment is being addressed in #962 since that's where the validation was loosened.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e mjs commands src/background/ | xargs cat
echo "---"
cat src/content-script/menu-tools/index.mjs

Length of output: 4281


@octo-patch Both fixes in commit 810e8d3 look correct and well-structured.

src/background/commands.mjs — the unhandled-rejection guard is a clean mirror of the menus.mjs pattern: the action is called synchronously (user-gesture preserved), and .catch() is conditionally attached only when the returned value is a thenable. ✅

src/content-script/menu-tools/index.mjs — the openSidePanel.action guard is solid:

  • Dropping the async wrapper and explicitly returning the chrome.sidePanel.open() Promise lets callers observe it. ✅
  • The typeof chrome === 'undefined' || !chrome.sidePanel?.open guard correctly handles Firefox and any other non-Chromium environment that loads the same bundle synchronously-safe. ✅
  • Returning Promise.reject(...) instead of throwing keeps the error flow uniform through the caller's .catch(). ✅

All three concerns from this thread (unhandled rejection in menus.mjs, unhandled rejection in commands.mjs, and synchronous throw on missing API) are now properly addressed. The Claude session-key fix being tracked in #962 is the right call too.

coderabbitai[bot]

This comment was marked as resolved.

`Browser.commands.onCommand`'s `tab` parameter is documented as optional, so
an action that dereferences `tab.*` (e.g. `openSidePanel` reading
`tab.windowId` / `tab.id`) can throw synchronously before returning a
Promise. The previous `.catch` only observed Promise rejections, so a
synchronous throw would still surface as an uncaught error in the background
script.

Wrap the action invocation in try/catch and log+return on synchronous
failure, mirroring the async rejection handling.

Addresses CodeRabbit review on PR ChatGPTBox-dev#963.
@octo-patch
Copy link
Copy Markdown
Author

Thanks for the additional finding, @coderabbitai! Pushed commit 0f82ecd that wraps the menuConfig[command].action(true, tab) call in src/background/commands.mjs with try/catch.

Browser.commands.onCommand documents tab as optional, so an action that dereferences tab.* (e.g. openSidePanel reading tab.windowId / tab.id) can throw synchronously before returning a Promise. The existing .catch() only observed Promise rejections, so a synchronous throw would still surface as an uncaught error in the background script. The new try/catch logs and returns on synchronous failure, mirroring the async rejection handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me verify the current state of the fix.


🏁 Script executed:

#!/bin/bash
cat src/background/commands.mjs
echo "---"
cat src/content-script/menu-tools/index.mjs | grep -A 20 "openSidePanel"

Length of output: 2518


@octo-patch The try/catch in commit 0f82ecd is the right call. The fix is clean and complete:

  • Synchronous throws (e.g. tab is undefinedtab.windowId throws before the chrome.sidePanel.open call ever runs) are now caught and logged, then the handler returns early. ✅
  • Async rejections (e.g. chrome.sidePanel.open() rejects for a non-gesture reason) are still caught by the existing .catch() handler. ✅
  • Both paths produce the same log message (failed to run command action "${command}"), keeping error handling uniform. ✅

The full error-handling chain across menus.mjs, commands.mjs, and menu-tools/index.mjs is now solid. Nice work closing the loop on all the edge cases!

devin-ai-integration[bot]

This comment was marked as resolved.

contextMenus.onClicked.tab is optional per the Chrome API ('If the
click did not take place in a tab, this parameter will be missing').
The openSidePanel action dereferences tab.windowId/tab.id, so it can
throw synchronously, which the existing .catch() Promise handler
would not catch. Mirror the pattern already used in
src/background/commands.mjs by wrapping the call in try/catch.

Addresses devin-ai-integration[bot] review on src/background/menus.mjs:23.
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.

Cannot open side panel

1 participant