All pages
On this page

Sharing a conversation

Sharing gives a conversation a public, read-only link. Anyone you send it to can read the transcript in their browser without signing in, and follows the conversation as it goes on. They cannot send, stop, change or open anything else on your relay. You decide what the link shows: single messages can be hidden, and the relay host's paths are filtered out. You always see the full conversation yourself, and Settings → Sharing lists every link with a log of who opened it.

The link is public

Anyone who has the address can read the shared transcript, and it can be passed on. The relay does not ask who opens it. Treat it like a password for that one conversation, and read the conversation with Preview as shared before you send it.

Creating the link #

  1. Open the conversation.
  2. Open its ⋯ menu and choose ➡️ Share conversation.

There is no dialog. The relay creates the link and copies it to your clipboard, and a short notice says so:

Share link copied to clipboard.

If the browser does not let the page write to the clipboard, the notice shows the address itself instead, and you copy it from there.

The address looks like https://relay.example.com/shared/ followed by a long random token (64 hexadecimal characters). The host is the one you used to reach the relay; behind a proxy on another machine it is the one the proxy reports, once the relay trusts that proxy. If the relay is served under a subpath (remotePath in config.json), the subpath is part of the address.

Choosing ➡️ Share conversation again does not make a second link: while the conversation has an active link, you get the same address back. Any conversation can be shared, an archived one too.

Your shared links Added in 0.9.11 #

Settings → Sharing lists every conversation that has a link right now, with when it was shared (and renewed), when it was last opened, how many times and by how many viewers. A click on the title opens the conversation. Each row has:

Button What it does
Copy link Copies the link.
Renew link Gives the conversation a new link, after a confirmation, and copies it. The old link stops working at once. The access log carries over to the new link. Use it when a link went further than it should.
Stop sharing Ends the link, after a confirmation. It stops working at once, also for anyone reading right now, and its access log is deleted.
Access log Unfolds the access log of the link.

While the open conversation is shared, its ⋯ menu also has ⛔ Stop sharing, which asks first as well.

After you stop or renew #

A stopped or renewed link answers a plain 404, the page and the data alike, exactly like a link that never existed. A shared page that is still open on it stops at its next refresh, within about a second. Choosing ➡️ Share conversation again after stopping creates a new link with a new token; an old address never comes back.

The same works without the app, with the auth token (Access) as a header, for example to stop sharing from a script:

curl -X DELETE -H "Authorization: Bearer YOUR_TOKEN" \
  https://relay.example.com/api/conversation/CONVERSATION_ID/share

POST to the same address creates the link, POST …/share/renew renews it, and GET /api/shares lists them.

The access log #

Each row of the list unfolds into its access log, newest first. An entry is one open of the shared page, not every refresh: the page reloads the conversation about every second, and only the first load counts. A second load by the same viewer within 10 seconds counts once. A viewer who comes back after 30 minutes without reading counts again, for example a page that slept in a background tab. A script that reads the link without the page counts the same way, so nobody reads the link without an entry.

Part of an entry Where it comes from
Time When the page was opened.
IP address The viewer's address, as the relay sees it. Behind a proxy that is the address the proxy reports, if the relay trusts it; otherwise every viewer has the proxy's address. IP not stored when Store viewer IP addresses was off.
Country The country code a Cloudflare tunnel adds, taken only from a trusted proxy. Empty otherwise.
viewer … A short keyed hash of address and browser, the same for every open by the same viewer, also when the address is not stored.
Browser The user agent the viewer's browser sent, as text.

The list shows the newest 200 opens and says how many there are in all.

The log carries over when you renew the link. It is kept as long as the link is shared, and deleted when you stop sharing or delete the conversation.

Store viewer IP addresses under Access log (on by default) decides whether new entries keep the address. Switched off, new entries keep none, and neither does the relay's own log line for a shared read. If addresses are already stored, OAR asks whether to remove them too: Remove them clears them from every access log entry and from the status log of shared reads, the entries themselves stay; Keep them leaves them. The setting applies to all links of the relay.

Paths and file names Added in 0.9.11 #

A shared view does not carry the relay host's paths in full. The relay filters everything it serves to a link, by the setting Paths and file names in Settings → Sharing:

Option A viewer sees for /srv/projects/shop/src/cart.js
Show every path and file name the path as it is
Hide the path, show the file name (the default) cart.js
Hide paths and file names ‹hidden›

The setting applies to every link of the relay, from the next refresh of a shared page. Your own view always shows everything.

What the filter catches:

  • Always: paths in tool arguments, file mentions (@file:, @folder:, relative ones too), attachment names, sub-agent records and the file headers of diffs.
  • In free text, that is replies, thoughts, tool output and code blocks: the folders the relay knows (the conversation's working folder, the workspace roots, your home folder, the relay's own data and upload folders) with whatever follows them, absolute Windows and Unix paths, ~/… paths, and your user name between separators. This is a best effort.
  • Not caught: relative paths in free text (src/cart.js), one-level paths such as /tmp, web addresses, and anything inside a picture.

Preview as shared #

🔍 Preview as shared in the conversation's ⋯ menu opens the conversation as its link shows it, without creating a link and without an entry in any access log. Green marks what viewers get instead of a path. Amber marks what still looks like one: a relative path with a file extension, a home/… or Users/… piece, your user name as a word, and with Show every path and file name every absolute path. Read the amber marks, and hide a message that gives away more than it should.

When you archive, delete or compact #

  • Archive does not end the link: an archived conversation stays readable through it, and unarchiving changes nothing.
  • Delete ends it, and deletes its access log: the link answers 404.
  • Compacting (see Compacting) archives the conversation and continues in a new one. The link stays with the old, archived conversation and keeps showing what it showed. The new conversation has no link, so it is not shared until you share it.

See The conversation list for archiving and deleting.

What a viewer sees #

The link opens the conversation in the normal chat layout without the sidebar and without the composer. The title is the conversation's title.

Part of the conversation In the shared view
Your messages and the replies Shown, with paths filtered.
Tool activity lines and thoughts Shown, with paths filtered.
Attachments and images Shown, including images an agent generated, under a filtered name. The viewer can open the ones on messages that are visible to them.
Sub-agent rows Shown. Added in 0.9.11 A row opens the sub-agent's record, read only, without Stop, and filtered like the rest.
Preview cards Shown with their label only, without the link. A preview link quoted anywhere in the text reads [preview link removed]. A shared conversation does not publish the previewed app.
Relay event rows (from watching sessions) Shown as event rows. The text names the watched session.
Messages that came from another relay Shown with a badge. The relay's address and the other conversation's id are removed; its name, the other conversation's title, the provider and the model stay.
Question cards and plan boards Not shown. They are not part of the data a viewer gets, so a viewer sees only what the agent wrote around them.
Images and file links in message text that point at files on your relay They do not load for a viewer: those files need your sign-in.
Messages you hid Not sent at all.
Pins and the 🙈 list Not shown: they are yours.
Working folder, session and runtime details Not sent.
Search, the context button, the model and mode pickers, fullscreen, app install Not available.

The data the page loads also carries the model used for each message. The shared page asks search engines not to index it.

Live updates #

The shared page reloads the conversation every 0.9 seconds while it is in the foreground, so new messages appear as the conversation continues. It does not open a live connection to the relay, and a page in a background tab does not reload. A viewer who scrolls up to read is not pulled to the end.

Watchers #

Each open shared page tells the relay every 12 seconds that it is there. You see how many are reading as a tooltip on the conversation's title in your own view, for example 2 watchers currently reading this conversation. The indicator is there only while at least one is reading. A viewer who has been silent for 45 seconds drops out of the count.

Hiding single messages #

Each message in your own view has a Hide button, shown when you hover the message (on touch screens, tap the message). Press it and the message leaves the shared view. It is not deleted. Each message is hidden on its own: hiding your question does not hide the reply to it, and the other way round. A reply's tool activity and thoughts go with the reply.

  • In your view the message stays complete and is marked Hidden from shared viewers. Its button now reads Unhide; press it and the message is visible to viewers again.
  • A viewer sees nothing in its place. The message is simply absent from the transcript. Attachments of a hidden message cannot be fetched through the link either, unless the same file is attached to a message that is visible.
  • A notice confirms each change: Message hidden from shared viewers. or Message visible to shared viewers.
  • Hide is not offered for a message that is still queued, or for the messages of the turn that is running. Hide them after the turn.

A hidden message is hidden from every link of the conversation, including a new link created later. The mark survives a restart of the relay.

The 🙈 list Added in 0.9.11 #

While a conversation hides messages, a 🙈 button with their count sits next to 📍 in its header (never in the shared view). It opens the list of hidden messages, like the pinned ones:

  • A row jumps to its message, also one far up in the history, and has Unhide.
  • Unhide all asks first. A message whose turn is still running stays hidden, and the list names it.
  • The count and the list cover messages that are not loaded yet, and other tabs and devices follow a change without a reload.

What a viewer can do #

The ⋯ menu in the shared view holds one entry, ☀️ Day mode or 🌙 Night mode, which switches the page's theme for that viewer. Beyond that the viewer can scroll the transcript, load earlier messages, open folded activity, open sub-agent records and open attachments. The input field is disabled and reads Shared conversation (read-only). Nothing a viewer does changes the conversation.

Limits #

Viewers counted at once, per conversation 200 (sharedPresenceMaxPerConversation).
Viewers counted at once, in total 5000 (sharedPresenceMaxGlobal).
Expiry None. A link works until you stop sharing, renew it, or delete the conversation.

Both limits are in config.json. When a limit is reached, the viewer who has been idle longest is dropped from the count to make room for a new one. The limits cap the count, that is, the number you see as watchers; the transcript request itself does not check them, so a viewer beyond the count can still read.

Reaching the relay #

The shared page and its images are served by your relay, not by oar.sh or any other service. A viewer must be able to reach the relay's address from where they are. A relay that listens only on your own machine (localhostOnly) cannot be opened by anyone else. To share beyond your network, reach the relay through a tunnel.

If the tunnel has access rules, they apply to the shared link too. With a Cloudflare Access application over the hostname, viewers are asked to sign in and the link does not work for them, unless the application lets /shared/* and /api/shared/* through without a login. Do not put a cache rule on /api/shared/* either: the page reloads that address to follow the conversation, and a cached answer would keep showing an old state.

By provider #

Nothing differs by provider. Sharing reads the stored conversation, so it works the same for GitHub Copilot, OpenAI, Claude, Claude Cloud, Cursor, Grok and DeepSeek chats, and for OpenAI Image chats, whose generated images are served through the link. Added in 0.9.11 A reply carries the red ran on … chip in the shared view only when it really ran on another provider than the conversation's own.

If it does not work #

What you see Cause and fix
The link answers 404 Sharing was stopped, the link was renewed, or the conversation was deleted. Settings → Sharing lists the links that work.
The viewer is asked to sign in, or sees an old state Cloudflare Access or a cache rule in front of the relay. See Reaching the relay.
Every entry of the access log has the same address A proxy on another machine that the relay does not trust. Set trustProxy.
A path still shows in the shared view It is relative, or inside a picture. Check with Preview as shared and hide the message.

For the transcript the viewer reads, see The transcript.