fix(cli): stop promising a claim link to signed-in publishers - #3545
fix(cli): stop promising a claim link to signed-in publishers#3545miguel-heygen wants to merge 2 commits into
Conversation
The publish consent notice always said "anyone with the URL can open the published project and claim it after authenticating", but that is only true for an anonymous publish. When a credential resolves, the server returns claimed=true with an empty claim_token and the CLI prints no claim URL, so a signed-in user was left hunting for a token that was never issued. Resolve the credential once before the notice and branch the wording on it. The --update/--space gate now reuses that same resolution instead of calling tryResolveCredential a second time.
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Approving exact head 710a58ea.
Verified that credential resolution is single-sourced, returns only a credential or null, and drives both the consent text and existing --update/--space authentication gate. Anonymous copy remains unchanged; signed-in copy no longer promises a claim link. The focused declined-prompt tests cover both branches, and no publish/import work runs after a declined confirmation.
— Jerrai
jrusso1020
left a comment
There was a problem hiding this comment.
Requesting changes at 710a58ea, on something that came along with the copy fix rather than on the fix itself. The claim-link problem is real and the branch is the right shape. I would take this immediately with the exposure sentence restored.
The signed-in notice no longer says the project is world-readable
Two edits combine, and the second is easy to miss because it sits outside the ternary:
- The shared bold line lost the word "public".
publish.ts:125onmainreads"...creates a stable public URL.";:129at this head reads"...creates a stable URL.". Thatconsole.logis unconditional, so it changes what both branches print. - The dim line is replaced rather than supplemented on the signed-in branch. "Anyone with the URL can open the published project and claim it after authenticating." becomes "You are signed in, so the project is owned by your account on publish. There is no claim link."
Net effect for a signed-in publisher: the consent prompt they approve before anything uploads no longer states that the artifact is reachable by anyone holding the URL. Nothing later fills the gap either. The published.claimed result block prints Project / Files / URL / Status, and the word "Public" appears only on the anonymous branch (publish.ts:260). So a signed-in user now goes from prompt to published with no exposure disclosure anywhere in the flow.
I checked that the disclosure is true rather than defensive boilerplate before asking for it back. GET /v1/hyperframes/projects/<project_id>/public carries no @HeyGenAPI decorator at all (render_routes.py:161-162), so it is unauthenticated, and get_public_project does not filter on is_public. Any published project is readable by anyone with its id, --public or not, signed in or not. Ownership is not privacy here, and the new wording invites reading it that way.
Suggested shape: keep the exposure sentence on both branches and vary only the claim clause.
Anyone with the URL can open the published project and claim it after authenticating.
Anyone with the URL can open the published project. It is owned by your account on publish, so there is no claim link.
Then either restore "public" to the bold line, or drop it deliberately and say so in the description.
"The signed-out wording is unchanged" is not accurate
Both that line and "its wording is byte-identical to what shipped before" are false for the bold line, per the :125 / :129 comparison above. The before/after blocks in the description do show the change ("stable public URL" in Before, "stable URL" in After), but the After block is labelled "when a credential resolves", so a shared edit reads as if it were scoped to the signed-in branch.
Neither new test pins that line. offers the claim step when publishing anonymously asserts only toContain("claim it after authenticating"), which the dim line still satisfies on its own, so the signed-out change passes CI unnoticed. Worth an assertion on the first line in whichever direction you land.
Checked and clear
- The credential hoist adds no new failure surface, and the description is right about why.
publishProjectArchivealready callstryResolveCredential()on every publish including anonymous (publishProject.ts:719), so a broken credential store already aborted a plain publish. This only moves the abort earlier, before the prompt instead of mid-upload, which is the better place for it. I went looking for a regression here and there isn't one. - Reusing the hoisted value in the
--update/--spacegate is genuine de-duplication, not a semantic change: same function, same options, and nothing between the two former call sites mutates the store. - The anonymous dim line is byte-identical, so the claim-token path itself is untouched.
CI has since settled green on this head: 41 checks, none in flight, none failed. So this verdict is on the copy, not on a red lane.
Review by Rames
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Superseding my earlier approval at the same exact head 710a58ea.
The claim-link correction is sound, but the signed-in consent prompt now omits the material disclosure that anyone holding the URL can open the published project:
- the shared headline changed from “stable public URL” to “stable URL”; and
- the signed-in branch replaces, rather than supplements, the URL-accessibility sentence.
The public read endpoint is unauthenticated, and ownership does not make the project private. Please preserve the exposure disclosure in both branches while varying only the claim-token clause—for example: “Anyone with the URL can open the published project. It is owned by your account on publish, so there is no claim link.”
Also correct the description’s claim that signed-out wording is unchanged, or restore the shared headline, and add a focused assertion for the shared/exposure copy.
No functional concern with the credential-resolution hoist.
— Jerrai
The claim-link fix dropped "public" from the shared bold line and replaced, rather than supplemented, the dim line on the signed-in branch. A signed-in publisher then approved the upload without being told anywhere in the flow that the artifact is reachable by anyone holding the URL: the owned result block prints Project / Files / URL / Status, and "Public" only appears on the anonymous branch. The disclosure is true for an owned project, not defensive boilerplate. The published-project read is the one hyperframes route with no authentication decorator, so any URL holder can open it; --public governs whether the CLAIMED session is public in the web app, not whether the link is readable. Restore "stable public URL" and lead the signed-in line with the same exposure sentence, keeping only the claim half branch-specific. A parameterised test now asserts both branches carry it.
|
Valid, and I checked the exposure claim at the source rather than taking it from the review. Fixed at
|
What
hyperframes publishno longer tells a signed-in user their project can be claimed. The consent notice now branches on whether a credential resolves, and both branches keep the exposure disclosure.Before (every publish, signed in or not):
After, when a credential resolves:
The signed-out output is byte-identical to
main, bold line included. Only the claim clause varies between branches.Why
The notice described the anonymous path only. An authenticated publish comes back
claimed: truewith an emptyclaim_token(packages/cli/src/utils/publishProject.ts:69-76), and the result block prints no claim URL in that branch (packages/cli/src/commands/publish.ts:214-247) because there is nothing to claim. So a signed-in user read a promise of a claim link, then went looking through the output for a claim token that the server never issued.The docs already get this right (
docs/packages/cli.mdx: "You can publish while signed out. The printed URL then carries a claim token"). The CLI prompt was the one surface asserting it unconditionally.How
Hoist the credential resolution above the consent block and pick the claim clause from it. The
--update/--spaceauthentication gate now reuses that same value instead of callingtryResolveCredential()a second time, so one resolution owns "is this publish authenticated".Resolution moves earlier for a plain publish, which previously only resolved credentials inside
publishProjectArchive. Same call, same failure surface; a broken credential store now fails before the upload starts rather than during it.The exposure sentence stays on both branches. It is load-bearing, not boilerplate:
GET /v1/hyperframes/projects/<id>/publiccarries no@HeyGenAPIdecorator (the/claimroute directly below it does), and its handler does not filter onis_public, so any published project is readable by anyone holding its id.--publicgoverns whether the claimed session is public in the web app, not whether the link is readable. Ownership is not privacy here.Review history
An earlier head (
710a58ea) dropped "public" from the shared bold line and replaced rather than supplemented the signed-in dim line, which left a signed-in publisher approving an upload with no exposure disclosure anywhere in the flow. Caught in review and fixed atf86f39ef2. The description above describes the current head.Test plan
packages/cli/src/commands/publish.test.ts: two cases drive the real command with a declined prompt and assert the claim clause per auth state, plus a parameterised case asserting both branches printcreates a stable public URLandAnyone with the URL can open the published project. That last one exists because the first version of this PR regressed the disclosure and no test caught it.bun run --filter @hyperframes/cli test -- src/commands/publish.test.ts→ 11 passed.bunx tsc --noEmit -p packages/cli/tsconfig.json→ clean. oxlint + oxfmt clean.Not covered: the signed-out prompt was not re-run against a real logged-out machine. The unit test covers that branch, and its output is byte-identical to
main.