Repository navigation
fix(deploy): a source deploy of a private service no longer exits 1 - #383
Conversation
The platform answers url '' for a private-only HTTP service (insta-compute adapter), and the archive-deploy poller treated that as a missing URL unless --port 0 was passed, so a deploy that went live exited 1 with "no image or URL". Accept an empty url; keep refusing a missing image, a non-string url, and a v6-endpoint deploy that comes back with neither a URL nor a host. The result line now says "no public URL: a private service" instead of calling it a worker. Refs SUP2-536, SUP2-543. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
andy-bot · r1 summary Stops |
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
jwfing
left a comment
There was a problem hiding this comment.
Summary
A tight, well-scoped fix: the archive-lane poller stops treating an empty url as a missing answer, and deployedTarget stops calling every URL-less service a worker — no Critical findings, but the surviving v6 guard is narrower than the handoff's own Constraints line claims.
Requirements context
This repo has no /docs/superpowers/ (no docs/ tree at all) — assessing against the PR description, its pr-shepherd handoff block, and .claude/skills/developing-insta-cli/SKILL.md, which is where this repo keeps its conventions (DI-injected runners, pure exported helpers unit-tested, one command per file).
Verified locally at 80a2bec in a clean workspace: npm run typecheck clean, npx vitest run = 106 files / 2247 passed + 1 skipped (the body says "2248 tests pass" — it's 2248 collected; trivial).
I also ran six mutation checks against the two touched test files — all six went red, so the new behaviour really is pinned: dropping isV6Endpoint(opts.endpoint) from the guard, dropping the whole empty-url clause, dropping typeof url !== 'string', dropping !image, forcing port === '0' true, and dropping the 'a worker or ' branch.
Critical
(none)
Suggestion
Functionality — the v6 refusal now only fires when --endpoint was passed on this run (src/deploy-archive.ts:170-176).
The new discriminator is the flag, isV6Endpoint(opts.endpoint). But this file's sibling comment at src/commands/deploy.ts:33-35 states the normal case explicitly: on a redeploy the user does not re-pass --endpoint — the service keeps the endpoint it has, and the platform reports it back beside endpointHost (instacloud-platform#681). So for every redeploy of an existing tcp/udp/tcp+udp service, a live result carrying neither a url nor an endpointHost is now accepted, and the CLI prints (no public URL: a private service — ...) and exits 0.
That is precisely the state the handoff's Constraints line says must stay refused ("An empty URL must still be refused for a v6-endpoint deploy with no host"), and it's the mirror image of the bug being fixed: a v6 deploy that genuinely failed to get an address is reported as a success, with a message asserting it's a private HTTP service.
The poll body already carries the discriminator the flag can't supply — res.body?.endpoint is read two lines above (src/deploy-archive.ts:169, currently gated behind endpointHost &&). Widening the guard to something like (!url && !endpointHost && (isV6Endpoint(opts.endpoint) || isV6Endpoint(res.body?.endpoint))) keeps the SUP2-536/543 fix intact (a private HTTP service reports endpoint: 'http' or nothing) while restoring the refusal for a v6 service on a modern platform. The irreducibly ambiguous case then shrinks to "old platform + v6 redeploy + no flag", which is a fair thing to let through.
Software engineering — no test for the no-flag v6 redeploy (test/endpoint-tcp.test.ts:96-103). The control was updated to pass endpoint: 'tcp', which is correct for what it now asserts, but nothing pins what happens to the same body without the flag. Whichever way you decide the point above, a case for { port: '7000' } + { url: '', endpoint: 'tcp' } + no host would record the intent rather than leaving it to be re-derived.
Functionality — the hint could name the command built for this (src/commands/deploy.ts:46). insta service list does show it (src/commands/services.ts:248 prints private <host>:<port>), so the hint isn't a false promise — but only when the platform reports private_host. insta compute public-access (src/commands/private-network.ts:30-46) is the command dedicated to exactly this question: it prints the private address, the private_fqdn, the "its URL answers 403" explanation, and an honest "none yet" line when there is no address. Naming it (or both) lands the user on more of what they need after a private deploy.
Information
--jsonconsumers inherit the old ambiguity (src/commands/deploy.ts:224): a successful private-service deploy now emits"url": ""with no accompanying signal, so an agent that reads an emptyurlas failure sees the same confusion the text lane just shed. The handoff's "Known gaps" covers the human-readable half; the JSON half is worth the same follow-up (apublic: false/privateHostfield from the poll would close both at once).- Security: no security-relevant changes. No new user input reaches SQL, shell or an HTTP path; no new dependencies; no auth/authorization check touched; nothing new logged or returned. The only validation change is relaxing a check on a platform-returned field, and the fallout is a wrong success message, not an injection or disclosure surface.
- Performance: no performance-relevant changes. Both edits are branches inside existing pure code — no new requests, queries, loops or allocations, and the poll cadence is untouched.
- Style matches the file throughout: the comments explain why rather than restating the condition, and both changed helpers stay pure and directly unit-testable, per the repo's convention.
Verdict
approved — no Critical findings. (Informational: the explicit GitHub approval remains a human action.) The v6-redeploy narrowing is the one I'd genuinely like answered before merge, but the net change strictly improves on today's behaviour, so it's a Suggestion, not a blocker.
…ts a v6 endpoint A redeploy of a tcp/udp service passes no --endpoint, so the guard also checks the endpoint the poll reports. The private-service hint now names `insta compute public-access <service>`. Co-Authored-By: andy-bot <andy-bot@users.noreply.github.com>
|
@jwfing thanks. On your review:
Since the approval, the changes are this guard, one test, and the hint text. typecheck clean; 2248 tests pass. |
What
insta deploy <dir>(archive/source lane) exits 1 with "the deploy finished but the platform returned no image or URL" after a successful deploy of a private-only HTTP service. Two customer tickets (SUP2-536, SUP2-543): both deploys werelive, new image active, CLI said failure.Cause: the platform's insta-compute adapter deliberately returns
url: ''for a service with public access off (repos/platform/src/adapters/insta-compute.ts,privateOnly). The poller insrc/deploy-archive.tsonly accepted an empty URL for--port 0or a returnedendpointHost.Change
src/deploy-archive.ts: an emptyurlis accepted. Still rejected: missing/empty image, non-string url, and a deploy that asked for a v6 endpoint (--endpoint tcp|udp|tcp+udp) but got neither a URL nor a host.src/commands/deploy.tsdeployedTarget: with no URL, prints(no URL: a worker)for port 0,(no public URL: a private service — ...)for a real port, and "a worker or a private service" when a redeploy passed no--port. The image lane uses the same function, so it stops calling private services workers too.endpoint: 'tcp'; threedeployedTargetcases.Testing
npm run typecheck,npx vitest run: 106 files, 2248 tests pass.Handoff
Intent: Stop the CLI from reporting a successful private-service source deploy as a failure (exit 1).
Rejected: Platform-side change to return a
public:false/private-host hint — the CLI can be fixed alone; a hint can follow.Constraints: An empty URL must still be refused for a v6-endpoint deploy with no host, and a missing image must still fail.
Known gaps: The result line doesn't print the private host itself (the poll response doesn't carry it).
🤖 Generated with Claude Code