Skip to content

feat: attach already-uploaded objects to a PR/issue by key or URL - #706

Merged
Zach Dunn (zachdunn) merged 1 commit into
mainfrom
claude/702-attach-existing-objects
Aug 18, 2026
Merged

feat: attach already-uploaded objects to a PR/issue by key or URL#706
Zach Dunn (zachdunn) merged 1 commit into
mainfrom
claude/702-attach-existing-objects

Conversation

@zachdunn

Copy link
Copy Markdown
Member

Problem

There was no way to attach an already-uploaded file to a PR or issue without
downloading and re-uploading it. Closes #702.

What changed

Server — new POST /v1/workspaces/:workspace/github/attach on the
canonical dual-auth surface (apps/api/src/routes/github-attach.ts,
github-attach.ts, github-attach-service.ts):

// request
{
  "source": "f/AbC123/hero.png",      // object key, or an uploads.sh URL
                                       // (storage host, embed host, /f/ page)
  "repo": "owner/name",
  "pr": 123,                          // exactly one of pr | issue
  "move": false,                      // optional, default false (copy)
  "filename": "hero.png"              // optional override
}

// response
{
  "key": "gh/owner/name/pull/123/hero.png",
  "url": "https://storage.uploads.sh/...",
  "embedUrl": "https://embed.uploads.sh/...",
  "pageUrl": "https://uploads.sh/f/...",
  "moved": false,
  "source": { "key": "f/AbC123/hero.png" },
  "comment": { "posted": true, "action": "updated", "count": 1 }
}
  • In-bucket R2 copy into the target's attachment prefix, honoring the
    gh/private/<id>/... layout when the repo resolves to private mode
    (same resolveGhKeyContextSafe used by promote).
  • Metadata merge is additive (PR feat: per-file metadata — PR/issue context, custom tags, and search #157 preserve mode): the source's
    existing D1 metadata rides along unchanged; gh.repo/gh.kind/
    gh.number/gh.ref are stamped fresh on top. This is the one deliberate
    divergence from github-promote.ts, which does a full-replace gh.*-only
    copy — an attach source can carry arbitrary metadata (path/viewport/
    state/a prior attachment's own tags) worth preserving.
  • Runs the normal managed-comment sync itself (postManagedComment) — one
    API call both attaches and refreshes the comment, unlike branch-staged
    promote which leaves that to a separate CLI call.
  • Same workspace-token auth (files:write) and implicit repo-link claim
    posture as the existing comment/promote routes.
  • Copy by default; move: true deletes the source only after a successful
    copy.
  • Idempotent: re-attaching the same source to the same target overwrites the
    destination key in place.

Only added to the canonical /v1/workspaces/:workspace/github/* vertical —
no legacy bearer-only twin, since this is a new route rather than a moved one.

CLI (packages/uploads) — uploads attach --pr/--issue <arg>...: an arg
that doesn't exist on disk is now tried as a workspace object key or
uploads.sh URL against the new endpoint instead of erroring
file not found. Existing on-disk files always win. New --move flag.
Mixed batches (local files + keys/URLs) work in one call.

$ uploads attach --pr 1250 --repo buildinternet/sunny f/_IzLjHsBCVYO/family-adler-top.webp

Hosted MCP (apps/mcp) — the promote tool gained an explicit keys
argument (array of keys/URLs), independent of and combinable with its
existing branch-staged sweep. One bad key degrades into attachFailures
rather than aborting the batch; branch is no longer required as long as
keys is given.

Tests

  • apps/api/src/routes/github-attach-route.test.ts — 12 cases (bad body,
    auth, 404 on missing source, copy + additive metadata, all three URL
    spellings, cross-workspace URL rejection, idempotent overwrite, move,
    issue target).
  • packages/uploads/test/commands-attach.test.ts — new describe block:
    key/URL attach, --move, mixed local+remote batch, updated not-found
    message, --move misuse guard.
  • apps/mcp/test/mcp-branch-promote.test.tskeys alongside/without
    branch, partial-failure degrade, updated required-args usage error.

pnpm test (4469 tests), pnpm typecheck, and pnpm check all pass.

Deviations from the issue text

  • CLI flag is --move (issue draft also mentioned --from-key as an
    alternative disambiguator for the positional arg — not needed since a
    URL/key is essentially never also a valid local path).
  • Server request shape uses pr/issue (mutually exclusive) rather than a
    generic kind/num pair, mirroring the CLI's own --pr/--issue flags.
  • No legacy bearer-only route twin was added (canonical surface only) — see
    the note above.

Relationship to #700 / #701

- apps/api: new POST /v1/workspaces/:workspace/github/attach endpoint
  (canonical dual-auth surface). Resolves a source key or uploads.sh URL
  (storage host, embed host, /f/ page) to a key in the calling workspace,
  in-bucket-copies it into the PR/issue attachment prefix (respecting the
  gh/private/<id>/ layout when it applies), merges D1 metadata additively
  (source's existing metadata preserved; gh.repo/gh.kind/gh.number/gh.ref
  stamped fresh), and runs the normal managed-comment sync. Copy by
  default; move: true deletes the source after a successful copy.
  Idempotent — re-attaching the same source to the same target overwrites
  the destination key in place. Same workspace-token auth and repo-link
  claim posture as the existing comment/promote routes.
- packages/uploads (CLI): uploads attach --pr/--issue <arg>... now treats
  an arg that does not exist on disk as a candidate object key or URL
  instead of erroring file not found, calling the new endpoint via a
  bounded-concurrency batch. Existing local files always win. New --move
  flag. Updated the not-found error message for a bad key/URL arg.
- apps/mcp: the hosted promote tool gained an explicit keys argument
  (alongside its existing branch-staged sweep) that reuses the same
  server-side attach logic; one bad key degrades into attachFailures
  rather than aborting the batch.
- Tests at each layer (server route, CLI command, hosted MCP tool) plus a
  changeset for the CLI package.
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5bf67a3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@buildinternet/uploads Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (2)
  • coderabbit:review
  • review
🚫 Excluded labels (none allowed) (1)
  • wip

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 80cadcb1-5362-4ea1-b6ef-268566f9012b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zachdunn
Zach Dunn (zachdunn) merged commit faa961c into main Aug 18, 2026
5 checks passed
@zachdunn
Zach Dunn (zachdunn) deleted the claude/702-attach-existing-objects branch August 18, 2026 22:51
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.

CLI/API: attach existing uploaded objects to a PR/issue by key or URL (server-side copy)

1 participant