Skip to content

fix(deploy): a source deploy of a private service no longer exits 1 - #383

Merged
v01dstar merged 2 commits into
mainfrom
fix/private-service-source-deploy
Oct 26, 2026
Merged

v01dstar merged 2 commits into
mainfrom
fix/private-service-source-deploy

Conversation

@v01dstar

Copy link
Copy Markdown
Contributor

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 were live, 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 in src/deploy-archive.ts only accepted an empty URL for --port 0 or a returned endpointHost.

Change

  • src/deploy-archive.ts: an empty url is 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.ts deployedTarget: 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.
  • Tests: private HTTP case accepted; missing image / non-string url still rejected; tcp control now passes endpoint: 'tcp'; three deployedTarget cases.

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

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>
@v01dstar

Copy link
Copy Markdown
Contributor Author

andy-bot · r1 summary

Stops insta deploy <dir> exiting 1 after a successful private-only HTTP service deploy (SUP2-536/543): the archive poller now accepts an empty url, and still refuses a missing image, a non-string url, or a --endpoint tcp|udp|tcp+udp deploy that got neither URL nor host. deployedTarget also stops calling private services workers. Worth checking: (1) a redeploy of an existing tcp/udp service with no --endpoint and an empty url plus no host now succeeds silently, since the v6 check keys off opts.endpoint. Is that OK? (2) The wording of the no---port redeploy case ("a worker or a private service") in deploy.ts.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/deploy-archive.ts Outdated

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • --json consumers 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 empty url as 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 (a public: false / privateHost field 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>
@v01dstar

Copy link
Copy Markdown
Contributor Author

@jwfing thanks. On your review:

  • v6 refusal on a redeploy: fixed in f7b55c6. The guard now also checks the endpoint the platform reports (isV6Endpoint(res.body?.endpoint)), so an empty URL with no host on a tcp/udp service is refused even without --endpoint. The only case still let through is an old platform + a v6 redeploy + no flag.
  • Test for the no-flag v6 redeploy: added in test/endpoint-tcp.test.ts ({ port: '7000' } + { url: '', endpoint: 'tcp' }, no host → rejected).
  • Hint: the no-URL message now names insta compute public-access <service> instead of insta service list.
  • --json url: "": out of scope here. The handoff lists the missing private-host signal as a known gap, and a platform public:false/privateHost field is the planned follow-up for the text and JSON output.

Since the approval, the changes are this guard, one test, and the hint text. typecheck clean; 2248 tests pass.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

@v01dstar
v01dstar merged commit e259900 into main Oct 26, 2026
3 checks passed
@v01dstar
v01dstar deleted the fix/private-service-source-deploy branch October 26, 2026 00:45
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.

2 participants