feat(pipes): team pipe sharing with versioning, auto-update, and unshare#3971
Merged
Conversation
Share a pipe with your team from the desktop app (E2E-encrypted team configs). Improves on the stale-closed #3738 design: - versioned shares: payload carries an integer version; re-sharing bumps it ("push update to team (vN)" appears when local content differs) - recipients' copies carry a `# team-shared:vN` marker (mirrors enterprise-managed pipes) and auto-update on version bumps while preserving each member's own on/off choice - new installs arrive OFF by default, read-only in the editor; "fork to edit" strips the marker and stops auto-updates - unshare now propagates: marked copies whose share disappeared are disabled (never deleted), gated on a successful configs fetch so a failed fetch can never mass-disable - security: only raw_content travels (the author's parsed config object can hold secrets and is no longer pushed); share keys are validated against path traversal before any filesystem write - enterprise managed pipes get the same removal fix: policy sync now always runs and disables enterprise-marked pipes dropped from the policy (pruning only when the server actually returned the field) Pure sync logic lives in lib/team-pipes.ts with unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Product decision: nobody asked for E2E on pipe shares, and the team key
management (invite-link fragments, keys that don't follow accounts,
missingKey limbo, unrecoverable data) was the main UX killer. Pipes are
workflow prompts, not credentials, and the enterprise managed-pipes path
is already server-readable. TLS + at-rest encryption still apply. E2E
stays for config types that carry real secrets (AI presets).
- plaintext envelope reuses the same /api/team/configs columns with a
sentinel nonce ("plaintext") — zero backend changes, the server treats
value_encrypted as an opaque string either way
- new pushConfigPlain in use-team (no team key required); fetchConfigs
now parses plaintext rows even when the team key is missing, so
members in key-limbo still receive shared pipes
- share/unshare UI gated on team admin role — the backend 403s
team-scope writes from members, so don't show them a dead button
(member-level sharing later = one-line backend loosening)
- mockup updated to match
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
louis030195
pushed a commit
that referenced
this pull request
Jun 10, 2026
team-pipes.test.ts imports from bun:test but was missing from both vitest's exclude list and the test:bun glob, so vitest tried to run it and failed to resolve bun:test (Frontend Tests red on main). Its two siblings (team-crypto, team-api-contract) were added in #3971 but this one was missed. Add it to both, matching the existing pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Lets team and enterprise plan users share pipes across their team from the desktop app, with versioned updates and clean unshare. Supersedes the stale-closed #3738 and fixes its gaps.
How it works
Share. A team admin shares one of their pipes from the pipe row menu. The payload goes through the existing team configs channel as a plaintext row (decision: no E2E for pipe shares, see below). Only
raw_contenttravels. The author's parsed config object is never pushed (it can hold secrets; #3738 shipped it). Share/unshare is admin-only for now because the backend gates team-scope config writes to admins; member-level sharing later is a one-line backend loosening.Install. Teammates get a local copy under
~/.screenpipe/pipes/<name>/marked with# team-shared:vN(same pattern as enterprise-managed pipes). It arrives OFF by default, the recipient opts in with the normal toggle. The editor is read-only for marked copies; "fork to edit" strips the marker, renames, and stops auto-updates. No team encryption key is needed to receive shares, members stuck in key-limbo still get them.Update. Each share carries an integer version. When the author's local content drifts from the shared copy, the menu shows "push update to team (vN)". Recipients' marked copies auto-update on version bumps, preserving each member's own on/off choice (mirrors
syncManagedPipessemantics). A 5 minute poll picks up changes while the app is open.Unshare. Deleting the share disables (never deletes) every marked copy on next sync, with a "no longer shared" badge. The sweep is gated on
configsFetched(new flag inuse-team) so a failed configs fetch can never look like "everything was unshared" and mass-disable pipes.Enterprise fix.
syncManagedPipesand its policy caller had a known gap: pipes removed from the policy stayed enabled on devices forever (the early-return on empty list plus thelength > 0gate). Policy sync now always runs and disables enterprise-marked pipes dropped from the policy. Pruning only happens when the server response actually contained themanagedPipesfield, so an older backend that omits it cannot mass-disable a fleet.Why plaintext (no E2E) for pipe shares
The E2E team-key ceremony (AES key only on desktops, invite links carrying the key in the URL fragment, keys that don't follow accounts, missingKey limbo, unrecoverable data when devices are lost) was the dominant source of team support pain, and it would have silently gated this feature: a member without the key receives nothing. Pipes are workflow prompts, not credentials, and the enterprise managed-pipes path (
enterprise_pipes) is already server-readable plaintext. TLS in transit and Supabase at-rest encryption still apply.Mechanically: the plaintext envelope reuses the same
/api/team/configscolumns with a sentinel nonce ("plaintext"), and the server already treatsvalue_encryptedas an opaque string, so zero backend changes.fetchConfigsparses plaintext rows without a key and still decrypts encrypted rows (AI presets keep E2E, they carry real API keys).Safety details
isSafePipeName) against path traversal before any filesystem writeCode layout
lib/team-pipes.ts: pure sync logic (plaintext envelope, marker parse/strip, enabled-flag handling scoped to frontmatter, version bump, install/update/skip planner), no React or Tauri importslib/__tests__/team-pipes.test.ts: 24 unit tests over that logiccomponents/settings/pipes-section.tsx: UI wiring (badges, menu items, read-only editor, sync effect)lib/hooks/use-team.ts:pushConfigPlain, plaintext-awarefetchConfigs,configsFetchedflaglib/hooks/use-enterprise-pipes.ts+use-enterprise-policy.ts: removal-on-unlist fixTest plan
bun test lib/__tests__/team-pipes.test.ts lib/__tests__/team-api-contract.test.ts(35 pass)bun run buildpasses (Next type checking on)/api/team/configsPUT accepts the plaintext envelope shape (route stores value_encrypted/nonce as opaque strings;config_type: "pipe"already allowlisted)🤖 Generated with Claude Code