Skip to content

fix(desktop): dedupe Nostr session deep link imports - #9918

Merged
DOsinga merged 9 commits into
aaif-goose:mainfrom
harrykamboj1:fix/nostr-deeplink-duplicate-import
Jun 30, 2026
Merged

fix(desktop): dedupe Nostr session deep link imports#9918
DOsinga merged 9 commits into
aaif-goose:mainfrom
harrykamboj1:fix/nostr-deeplink-duplicate-import

Conversation

@harrykamboj1

Copy link
Copy Markdown
Contributor

Summary

Fix duplicate Nostr session imports from goose://sessions/nostr deep links.
macOS could deliver multiple open-url events for one Chrome paste, triggering
several POST /sessions/import/nostr calls.

Changes:

  • main.ts: queue links while window loading, 2s burst dedup, align macOS with Windows behavior
  • App.tsx: in-flight + post-success dedup for Nostr deep link imports only

Testing

Manual testing on macOS:

  • Cold start via Chrome deep link → 1 session
  • App already running → 1 session
  • Repeated IPC simulation in DevTools → 1 API call
  • Manual Import Link in Session History → unchanged

Related Issues

Fixes #9917

Screenshots

Before: multiple identical sessions in Chat History from one link
Screenshot 2026-06-21 at 1 51 40 PM

After: single session imported
Screenshot 2026-06-21 at 1 56 50 PM

Signed-off-by: harrykamboj1 <singhharnoor116@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f0e62bc81

ℹ️ 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/main.ts Outdated
Comment on lines +526 to +528
if (!pendingDeepLinks.has(targetWindow.id)) {
pendingDeepLinks.set(targetWindow.id, url);
}

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 Avoid dropping newer pending deep links

When the target window is still loading, this has guard keeps the first pending URL and silently ignores every later extension/session deep link for the same window, even when the URL is different. During startup or a renderer reload, opening an extension link and then a Nostr/session link before react-ready means only the stale first URL is delivered at react-ready; the newer user action is lost. The dedupe should only collapse identical URLs or queue/replace distinct pending links.

Useful? React with 👍 / 👎.

@DOsinga

DOsinga commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the fix and the detailed issue (#9917) — nice to see a small, well-scoped change with repro and before/after screenshots.

A couple of things before this can land:

  1. Codex's P2 is valid and needs addressing. The new pendingDeepLinks.has() guard (in deliverExtensionOrSessionDeepLink, handleProtocolUrl, and the open-url new-window path) collapses distinct pending links, not just identical bursts. During startup/renderer reload, if an extension link is queued and then a sessions/Nostr link arrives before react-ready, the newer user action is silently dropped and only the stale first URL is delivered. The dedupe should only collapse identical URLs — distinct pending links should queue or replace.

  2. The renderer-side dedup overlaps the main-process burst dedup — please simplify. You now have three time windows in play: the 2s main-process burst (DEEPLINK_BURST_DEDUP_MS), the 5s renderer post-success map (NOSTR_IMPORT_SUCCESS_DEDUP_MS), plus the implicit in-flight import duration. The nostrImportInFlight in-flight guard earns its keep — it protects the skipBurstDedup: true pending-link path and the case where macOS fires events spaced further apart than the 2s window while an import is still running. But the 5s recentSuccessfulNostrImports map largely duplicates what the 2s main-process burst window already does. Can you either drop the post-success map and rely on in-flight + main burst dedup, or explain why both windows (and the differing 2s/5s values) are needed? Two independent magic windows for the same race is hard to reason about and maintain.

I'll snooze this for a few days to give you a chance to respond.

@harrykamboj1

Copy link
Copy Markdown
Contributor Author

Hi @DOsinga
Thanks for the review. I've addressed both points in the latest push:

  1. P2 / pendingDeepLinks: Replaced the has() guards with queuePendingDeepLink(), which only skips identical URLs and replaces the pending entry when a different deep link arrives before react-ready (latest user action wins during startup).

  2. Renderer dedup: Removed the 5s recentSuccessfulNostrImports map. Kept nostrImportInFlight for concurrent imports and the 2s main-process burst dedup in sendOpenSharedSession().

Let me know if you'd like anything else adjusted.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a5ec09466

ℹ️ 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/main.ts Outdated
}

const DEEPLINK_BURST_DEDUP_MS = 2000;
let lastSentSessionDeepLink: { url: string; at: number } | null = null;

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 Track all recent session links during burst dedup

With two different session/Nostr links opened during the same 2s macOS duplicate burst, this single-slot cache only suppresses adjacent duplicates. For an event order A, B, A, sending B overwrites A's timestamp, so the later duplicate A is sent to the renderer again and can still create a duplicate Nostr import. Keep recent send times keyed by URL until the burst window expires instead of storing only the last URL.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99fa652b3e

ℹ️ 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/main.ts Outdated
window.webContents.send('add-extension', deepLinkUrl);
} else if (parsedUrl.hostname === 'sessions') {
window.webContents.send('open-shared-session', deepLinkUrl);
sendOpenSharedSession(window, deepLinkUrl, { skipBurstDedup: true });

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 Deduplicate pending session links before replaying

When macOS delivers duplicate open-url events during cold start before any BrowserWindow is constructed, each handler can enter the no-window path, await createChat, and queue the same Nostr URL for a different loading window. Replaying those pending links with skipBurstDedup bypasses the new burst guard, so each window still sends open-shared-session and imports the same deep link. Let pending session replays use the burst dedup, or otherwise suppress duplicate pending URLs across windows.

Useful? React with 👍 / 👎.

Signed-off-by: harrykamboj1 <singhharnoor116@gmail.com>
@DOsinga

DOsinga commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Thanks for iterating on this so quickly! There's one open codex comment left — the cold-start race where the react-ready replay sends pending session links with skipBurstDedup: true, which bypasses the burst guard so two cold-start windows can still each import the same URL. Could you have another look at that path? Would love to get this in. 🙏

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 626adde010

ℹ️ 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/main.ts
log.info('[Main] Ignoring burst duplicate session deep link');
return;
}
recordSessionDeepLinkSend(url);

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 Defer session-link dedup until renderer readiness

When a goose://sessions/... URL arrives just after a window's main frame stops loading but before React has installed the open-shared-session listener, this records the URL even though the IPC sent on the next line can be missed. The macOS duplicate delivered once the renderer is ready is then treated as a burst duplicate and dropped, so the user gets no import; queue until react-ready or an acknowledgement before recording the URL as sent.

Useful? React with 👍 / 👎.

@harrykamboj1

Copy link
Copy Markdown
Contributor Author

Thanks @DOsinga — pushed an update addressing the remaining items:

Cold-start replay race (the open Codex comment): removed skipBurstDedup: true from the react-ready pending-link replay, so cold-start windows now send open-shared-session through the same burst dedup as every other path. Combined with the URL-keyed recentSessionDeepLinkSends map, two windows opened for the same link in one burst no longer double-import.

Defer dedup until renderer readiness (latest Codex P2): added a reactReadyWindows set. Extension/session deep links are now queued until the window emits react-ready, so we never record a URL as "sent" before the open-shared-session listener exists — the macOS duplicate that arrives once the renderer is ready is no longer mistaken for a burst duplicate and dropped.

Removed the now-unused skipBurstDedup option from sendOpenSharedSession since nothing sets it anymore.

@DOsinga DOsinga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the careful iteration here — you addressed all the codex feedback and the simplification requests cleanly. The two-layer dedup (burst dedup in main.ts keyed by URL, plus the in-flight guard in App.tsx) and gating delivery on reactReadyWindows is the right approach. Nice first contribution!

@DOsinga
DOsinga added this pull request to the merge queue Jun 30, 2026
Merged via the queue into aaif-goose:main with commit 452ea10 Jun 30, 2026
23 checks passed
lifeizhou-ap added a commit that referenced this pull request Jul 1, 2026
* main: (26 commits)
  Fix MCP app sandbox bridge lifecycle (#10064)
  fix(bedrock): send inference config (max_tokens, temperature) on Converse (#9889)
  feat(providers): support OpenRouter request parameters (#9276)
  Migrate local inference model management to ACP (#10124)
  (attempt to) fix disk space errors in linux release builds (#10024)
  feat: add --edit session flag to edit conversation before forking (#9799)
  feat: add iFlytek Spark and Astron MaaS providers (#9837)
  fix(desktop): dedupe Nostr session deep link imports (#9918)
  [codex] Add SessionStart hook parity outside CLI (#9970)
  feat(providers): add Fireworks AI declarative provider (#9990)
  fix(providers): don't retry deterministically-permanent 400s (thinking-block immutability) (#10005)
  fix(deps): downgrade pkcs8 to v0.10 to match sec1/pkcs1 v0.7 (#10119)
  chore(deps): bump actions/cache from 5.0.2 to 6.0.0 (#10051)
  Make OpenAI Responses API store param configurable (#10040)
  remove unsupported model (#10121)
  chore(release): bump version to 1.40.0 (minor) (#10099)
  move ollama provider into goose-providers (#9986)
  UI acp migratoin: Decouple desktop UI types from generated OpenAPI types (#10109)
  fix(otel): use async reqwest client so OTLP export works in `goose serve` mode (#10100)
  feat (acp): exposed available tools in acp schema (#10097)
  ...
lifeizhou-ap added a commit that referenced this pull request Jul 1, 2026
* main: (42 commits)
  Fix MCP app sandbox bridge lifecycle (#10064)
  fix(bedrock): send inference config (max_tokens, temperature) on Converse (#9889)
  feat(providers): support OpenRouter request parameters (#9276)
  Migrate local inference model management to ACP (#10124)
  (attempt to) fix disk space errors in linux release builds (#10024)
  feat: add --edit session flag to edit conversation before forking (#9799)
  feat: add iFlytek Spark and Astron MaaS providers (#9837)
  fix(desktop): dedupe Nostr session deep link imports (#9918)
  [codex] Add SessionStart hook parity outside CLI (#9970)
  feat(providers): add Fireworks AI declarative provider (#9990)
  fix(providers): don't retry deterministically-permanent 400s (thinking-block immutability) (#10005)
  fix(deps): downgrade pkcs8 to v0.10 to match sec1/pkcs1 v0.7 (#10119)
  chore(deps): bump actions/cache from 5.0.2 to 6.0.0 (#10051)
  Make OpenAI Responses API store param configurable (#10040)
  remove unsupported model (#10121)
  chore(release): bump version to 1.40.0 (minor) (#10099)
  move ollama provider into goose-providers (#9986)
  UI acp migratoin: Decouple desktop UI types from generated OpenAPI types (#10109)
  fix(otel): use async reqwest client so OTLP export works in `goose serve` mode (#10100)
  feat (acp): exposed available tools in acp schema (#10097)
  ...
lifeizhou-ap added a commit that referenced this pull request Jul 1, 2026
* main: (31 commits)
  test: generic validator for declarative providers (#10010)
  UI acp migratoin: Decouple desktop UI types from generated OpenAPI types (Part 2) (#10149)
  Remove MCP sampling support (#10087)
  Support TLS for ACP serve (#10088)
  feat (ui): Migrate dictation local model manager to ACP (#10131)
  Fix MCP app sandbox bridge lifecycle (#10064)
  fix(bedrock): send inference config (max_tokens, temperature) on Converse (#9889)
  feat(providers): support OpenRouter request parameters (#9276)
  Migrate local inference model management to ACP (#10124)
  (attempt to) fix disk space errors in linux release builds (#10024)
  feat: add --edit session flag to edit conversation before forking (#9799)
  feat: add iFlytek Spark and Astron MaaS providers (#9837)
  fix(desktop): dedupe Nostr session deep link imports (#9918)
  [codex] Add SessionStart hook parity outside CLI (#9970)
  feat(providers): add Fireworks AI declarative provider (#9990)
  fix(providers): don't retry deterministically-permanent 400s (thinking-block immutability) (#10005)
  fix(deps): downgrade pkcs8 to v0.10 to match sec1/pkcs1 v0.7 (#10119)
  chore(deps): bump actions/cache from 5.0.2 to 6.0.0 (#10051)
  Make OpenAI Responses API store param configurable (#10040)
  remove unsupported model (#10121)
  ...
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.

Opening goose://sessions/nostr deep link creates multiple duplicate session imports (macOS)

2 participants