Skip to content

fix(desktop): reuse existing window for recipe deep links#9289

Open
SarthakB11 wants to merge 3 commits into
aaif-goose:mainfrom
SarthakB11:fix/issue-9143
Open

fix(desktop): reuse existing window for recipe deep links#9289
SarthakB11 wants to merge 3 commits into
aaif-goose:mainfrom
SarthakB11:fix/issue-9143

Conversation

@SarthakB11
Copy link
Copy Markdown
Contributor

Summary

Clicking a goose://recipe/... or goose://bot/... deep link while Goose Desktop is already running was spawning a fresh BrowserWindow every time instead of routing the deeplink into the focused window. Three protocol entry points in ui/desktop/src/main.ts (app.on('second-instance'), processProtocolUrl, app.on('open-url')) each called createChat(app, { recipeDeeplink, ... }) unconditionally.

This adds an openRecipeDeeplink(url, openDir) helper next to processProtocolUrl in ui/desktop/src/main.ts. It picks the most-recently-focused BrowserWindow (falling back to the newest open window, then the oldest), restores and focuses it, and forwards the deeplink as an open-recipe-deeplink IPC payload (recipeDeeplink URL string, recipeParameters, scheduledJobId). When no window exists it falls back to createChat(...) and threads windowDeeplinkURL so the cold-start dir-picker path keeps its prior behavior. All three protocol call sites now go through the helper.

On the renderer (ui/desktop/src/App.tsx), a new open-recipe-deeplink IPC listener mirrors the existing recipeDeeplinkFromConfig branch in PairRouteWrapper: it calls createSession(getInitialWorkingDir(), { recipeDeeplink, allExtensions }), dispatches ADD_ACTIVE_SESSION, and navigates to /pair?resumeSessionId=.... The payload sends the raw deeplink URL string so the renderer can route through the same decodeRecipe path used at cold launch — the IPC contract stays compatible with createSession's existing recipeDeeplink: string signature.

Reuse is unconditional rather than opt-in; matches the behavior of the surrounding goose://extension / goose://sessions deeplinks.

Testing

Manual only. The change is pure routing in the Electron main process plus a new renderer-side IPC listener; there is no existing harness around main.ts deeplink dispatch. Reviewer test path: with Goose Desktop running, trigger goose://recipe/... from an external process and confirm the recipe loads as a new session in the focused window instead of opening a new one. Worth a quick check on both macOS (open-url) and Linux/Windows (second-instance).

Related Issues

Closes #9143

Screenshots/Demos (for UX changes)

N/A

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e6743c2e0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/App.tsx
Comment on lines +438 to +440
const newSession = await createSession(getInitialWorkingDir(), {
recipeDeeplink,
allExtensions: extensionsList,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pass the recipe config instead of the full URL

When a goose://recipe?... link is opened into an existing window, this passes the full URL to createSession, but createSession forwards recipeDeeplink directly to decodeRecipe, whose backend decoder expects only the base64 recipe config string (the cold-start path still passes deeplinkData?.config). As a result, any recipe/bot deep link handled through this new IPC path fails to decode instead of opening the recipe; extract and pass the config value here, or update the decode path to accept full Goose URLs.

Useful? React with 👍 / 👎.

@DOsinga DOsinga requested a review from lifeizhou-ap May 18, 2026 14:43
Signed-off-by: Douwe Osinga <douwe@squareup.com>

# Conflicts:
#	ui/desktop/src/main.ts
@DOsinga DOsinga added the needs_human label to set when a robot looks at a PR and can't handle it label May 18, 2026
@DOsinga
Copy link
Copy Markdown
Collaborator

DOsinga commented May 18, 2026

I merged origin/main into this branch and fixed the merge conflicts.

During conflict resolution, I also fixed a bug that Codex flagged: the IPC payload was sending the full goose://recipe?config=... URL as recipeDeeplink, but the renderer's createSessiondecodeRecipe path expects just the base64 config string (matching the cold-start path which passes deeplinkData?.config). This would have caused recipe decoding to fail at runtime when reusing an existing window.

The fix: recipeDeeplink: urlrecipeDeeplink: deeplinkData?.config in the IPC send.

Items for human reviewer to check:

  • recipeParameters and scheduledJobId are sent in the IPC payload but not consumed by the renderer handler. In the cold-start path, recipeParameters is read from appConfig by BaseChat.tsx for the ParameterInputModal. The IPC path doesn't thread these through, so parameterized recipes opened via deep link in an existing window won't get their URL query string parameter overrides. Worth deciding if this needs addressing.
  • The existingWindows[existingWindows.length - 1] ?? existingWindows[0] fallback is redundant (if the array is non-empty, the last element is always defined, and if it's length 1, last === first). Minor nit.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76ead475d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/desktop/src/App.tsx
Comment on lines +438 to +441
const newSession = await createSession(getInitialWorkingDir(), {
recipeDeeplink,
allExtensions: extensionsList,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward deeplink recipe parameters into session creation

This handler receives recipeParameters/scheduledJobId in the IPC payload but only passes recipeDeeplink to createSession, so recipe links opened in an already-running window lose their query-parameter context. That regresses behavior compared with the new-window path (which injects recipeParameters via appConfig) and means parameterized recipe links open without prefilled values in the parameter modal.

Useful? React with 👍 / 👎.

Comment thread ui/desktop/src/main.ts Outdated
Comment on lines +499 to +503
targetWindow.webContents.send('open-recipe-deeplink', {
recipeDeeplink: deeplinkData?.config,
recipeParameters: deeplinkData?.parameters,
scheduledJobId: scheduledJobId || undefined,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Defer recipe deeplink IPC until renderer is ready

The main process sends open-recipe-deeplink as soon as it finds any window, but in the startup path handleProtocolUrl can create a new window and then immediately hit this branch before AppInner has registered its listener. In that case the IPC is dropped and the deeplink is effectively ignored, so recipe launch can silently fail. This should be queued/deferred until the target window reports readiness (as done for other pending deep links).

Useful? React with 👍 / 👎.

@DOsinga
Copy link
Copy Markdown
Collaborator

DOsinga commented May 18, 2026

can you look at the codex comments

@SarthakB11
Copy link
Copy Markdown
Contributor Author

SarthakB11 commented May 19, 2026

Thanks @DOsinga, your recipeDeeplink: deeplinkData?.config fix is correct.

On the two Codex items:

P2 — recipeParameters not threaded. Will plumb them through AppEvents.ADD_ACTIVE_SESSION so BaseChat can read them on the warm-launch path (cold path keeps using window.appConfig).

P1 — IPC race when handleProtocolUrl spawns a new window. Will cache the payload in a pendingRecipeDeeplink module slot and replay it on reactReady, matching the existing pendingDeepLink pattern.

Pushing both shortly.

…derer is ready

Two follow-ups on aaif-goose#9143 per Codex review:

- recipeParameters URL overrides were sent in the IPC payload but the
  renderer dropped them. Introduce a small per-session store in
  ui/desktop/src/utils/recipeParametersStore.ts that App.tsx writes from
  the warm-launch IPC handler and ParameterInputModal reads as initial
  values (falling back to window.appConfig.recipeParameters for the
  cold-launch path that seeds it at preload).

- The bot/recipe IPC could race the renderer's listener when
  handleProtocolUrl created a fresh window on the same tick. Reuse the
  existing pendingDeepLinks/react-ready pattern: if the chosen window is
  still loading, queue the URL and dispatch the recipe IPC from the
  react-ready handler the same way extension/sessions deeplinks are
  already replayed.

Signed-off-by: SarthakB11 <sarthak.bhardwaj21b@iiitg.ac.in>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de72a03d65

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

onSubmit={setRecipeUserParams}
onClose={() => setView('chat')}
initialValues={
takeRecipeParametersForSession(chat.sessionId) ||
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep deeplink parameters stable while the modal is open

When a parameterized recipe link is opened into an existing window, this consumes and deletes the stored values during BaseChat render. On the next parent re-render while ParameterInputModal is still mounted, initialValues becomes undefined; because the modal's prefill effect depends on initialValues, it resets the form back to defaults and drops the URL-supplied values (and possibly user edits). The values should be read once into stable state/ref for the session instead of calling the destructive take... in the render path.

Useful? React with 👍 / 👎.

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

Labels

needs_human label to set when a robot looks at a PR and can't handle it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Open new links in same window, not new window

2 participants