---
title: Share Artifacts (publish local files and pasted content as links) (part 3)
---
# Share Artifacts (publish local files and pasted content as links) (part 3)

## What it is

This is part 3 of the [Share Artifacts (publish local files and pasted content as links)](share-artifacts.md) page. It carries the next stretch of the material on that page, moved here because a single page is capped at 40,000 characters.

## Where to find it

Reach this part through [Share Artifacts (publish local files and pasted content as links)](share-artifacts.md) — it lists every part and explains where the feature lives in the product. Everything below is reached from the same place.

## How it behaves

Everything below is the behaviour, detail and edge cases that belong to this stretch of the Share Artifacts (publish local files and pasted content as links) page.

### How it works

The publish pipeline is in [src/main/services/share-artifact/share-artifact-service.ts](../../src/main/services/share-artifact/share-artifact-service.ts) — single chokepoint for both the renderer IPC (`SHARE_PUBLISH_ARTIFACT`) and the CLI `POST /share/publish`. Both routes pass through `createShareArtifactService({ publisher })` so they share one storage publisher. After upload, `publishArtifact()` CONFIRMS the Firestore mirror doc landed (the public gate keys on it) and, on a permanent/persistent failure, fails the publish — rolling back the upload + the row — rather than return a URL that 404s (contract `publish-confirms-the-mirror`; the entry points no longer mirror publishes themselves).

The detector and HTML builder live at [src/main/services/share/share-artifact-builder.ts](../../src/main/services/share/share-artifact-builder.ts). Detection is extension-first, then magic-byte-second; the builder uses `marked` + `highlight.js` for Markdown / code, base64-inlines images and PDFs, and HTML-escapes plain text. The page shell also embeds the Omniscio app icon as a `data:` favicon, sourced from the shared [src/main/services/share/share-favicon.ts](../../src/main/services/share/share-favicon.ts) constant (the same icon used by conversation + digest shares).

Path validation against the active-workdirs + user-data allow-list is in [src/main/services/share/share-path-validator.ts](../../src/main/services/share/share-path-validator.ts) — symlinks resolve through `fs.realpath` before the check.

The share publisher is in [src/main/services/firebase/firebase-publisher.ts](../../src/main/services/firebase/firebase-publisher.ts) — but it holds **no Firebase key**. It is a thin seam over [share-relay-client.ts](../../src/main/services/share/share-relay-client.ts): every method delegates to the **`shareRelay` Cloud Function** ([firebase/functions/src/share-relay.ts](../../firebase/functions/src/share-relay.ts)), authed by the signed-in user's global-auth Firebase ID token. The relay (holding the `agentmc-share-uploader` key in Secret Manager) mints the share token + short-lived v4 **signed PUT URLs**, and the app uploads the bytes **straight to Cloud Storage** (bucket `agentmc-shares-artifacts`, prefix `shares/`) — so a large video has no 32 MB function-relay cap, and the per-kind security headers (content-type / cache-control / content-disposition) are pinned on the signed URL server-side. The relay also OWNS the token (closing the client-minted-token overwrite hole) and tears shares down (`unpublish` deletes the objects + mirror doc + events in one ownerUid-gated call). Because publishing needs sign-in, a signed-out publish fails with `sign-in-required` ("sign in to share"), never silently. (This retired the desktop's Firebase admin "master" key for Shares — the old client-side GCS REST + `google-auth-library` JWT uploader is gone.)

The Firestore **mirror** — the `shares/{token}` doc the public gate reads, written on publish/revoke/delete — is in [src/main/services/share/share-firestore-mirror.ts](../../src/main/services/share/share-firestore-mirror.ts). It keeps the same public API but routes every write/read/delete through the same **`shareRelay`** function (`relayMirrorWrite` / `relayMirrorRead` / `relayUnpublish`); the client sends only the policy fields and never the server-owned `ownerUid` / `jwtSecret` / `viewCount`. (This subsumed the earlier client-side REST/JWT mirror — itself the 2026-06-29 fix for a packaged build where firebase-admin's gRPC auth presented the wrong identity to Firestore, every consumer `7 PERMISSION_DENIED`, silently 404'ing _every_ share for ~12h: [postmortem](../../.claude/memory/postmortems/share-mirror-packaged-firebase-admin-auth-postmortem.md).) The publish-time confirmation + fail-loud rollback live in [share-publish-confirm.ts](../../src/main/services/share/share-publish-confirm.ts) (it reads the mirror doc back via `mirrorDocExists`, so a write-ack without a serve-visible doc never returns success — the 2026-08-11 transient-relay incident); the self-heal backfill — on startup AND every ~30 min — in [share-mirror-backfill.ts](../../src/main/services/share/share-mirror-backfill.ts); the single deduped "cloud features degraded" alert (shared with the agent-email / support-chat consumers) in [src/main/services/firebase/firebase-health-alert.ts](../../src/main/services/firebase/firebase-health-alert.ts).

DB schema is in [src/main/db/queries-share.ts](../../src/main/db/queries-share.ts) — `insertSharePending` → `markSharePublished` two-phase write, `findActiveByContentHash` for dedup, `revokeShare` / `deleteShare` for lifecycle.

The renderer entry points are [src/renderer/src/components/ui/FilePeekOverlay.tsx](../../src/renderer/src/components/ui/FilePeekOverlay.tsx) (the Share2 button in the file-peek toolbar), [src/renderer/src/features/shares/PastePublishModal.tsx](../../src/renderer/src/features/shares/PastePublishModal.tsx) (the paste modal, lazy-loaded from `ShareManager.tsx` per the heavy-modal lazy-load rule), and [src/renderer/src/components/ui/agent-markdown-helpers.tsx](../../src/renderer/src/components/ui/agent-markdown-helpers.tsx) (`CopyableLink` — the right-click menu on every file path / attachment link that Claude produces in chat). The three entry points all call the same `IPC.SHARE_PUBLISH_ARTIFACT` handler — the difference is only the payload shape (`sourcePath` vs `pastedContent` vs `attachmentRef`) and the `origin` discriminator that drives the `sourceType` telemetry split.

Telemetry fires from inside `publishArtifact()` on both branches — dedup-hit (`reused: true`) and fresh-publish (`reused: false`) — so the Stats view reflects all publish intents. The allow-list for the telemetry metadata is `['artifactType', 'reused', 'sourceType', 'autoStaged']` per the registry in [src/shared/feature-registry/](../../src/shared/feature-registry/).

Auto-stage lives in the same file as `autoStageExternal(sourcePath, userDataDir)`: if the first `validateSharePath()` returns `valid: false` with reason `'path outside allowed roots'`, **without** the internal `refusedSensitive` flag, AND `sourceType !== 'cli'`, the service copies the source bytes to `<userData>/published-pastes/<uuid>/<original base name>` and re-validates. Two details are load-bearing. (1) The branch may not key on the reason string alone: a path refused because its NAME is credential-shaped reports that same string on purpose, and rescuing one would strip the name the deny-gate keys on — so a refused secret is refused on every road, before the gesture check and before any byte is copied (`secret-refusal-is-never-rescued`). (2) The staged copy keeps the source's REAL leaf name inside a fresh UUID directory, so the post-stage re-validation still sees what the file is (`auto-stage-keeps-the-real-name`); naming it `<uuid><ext>` after the old flat scheme destroyed exactly that. The one sweep reclaims an aged stage directory whole. Any failure of the stage step (ENOENT, EACCES, ENOSPC) collapses back to `outside-allowlist` so the closed error-string contract is preserved. The full invariant set + safe-change checklist lives in [.claude/memory/contracts/share-artifacts-contract.md](../../.claude/memory/contracts/share-artifacts-contract.md).

## Related

The overview, the other parts, and everything else worth reading next all sit on [Share Artifacts (publish local files and pasted content as links)](share-artifacts.md).
