Skip to content

Add waitForState endpoint for blocking state transitions#167

Merged
hiroTamada merged 20 commits into
mainfrom
hypeship/add-wait-for-state
Mar 27, 2026
Merged

Add waitForState endpoint for blocking state transitions#167
hiroTamada merged 20 commits into
mainfrom
hypeship/add-wait-for-state

Conversation

@hiroTamada

@hiroTamada hiroTamada commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds GET /instances/{id}/wait?state=Running&timeout=30s endpoint that blocks until an instance reaches the target state, avoiding client-side polling.

Behavior:

  • Reuses existing GetInstance state derivation logic, polling internally every 1s
  • Default timeout 60s, max 5m, configurable via timeout query parameter
  • Returns immediately if already in target state
  • Returns early on terminal states (Stopped when not the target) or error states (Unknown)
  • Handles client disconnects via context cancellation
  • Instance deletion during wait returns 404

Response shape:

{
  "state": "Running",
  "state_error": null,
  "timed_out": false
}

Changes:

  • openapi.yaml — new WaitForStateResponse schema + /instances/{id}/wait endpoint
  • lib/oapi/oapi.go — regenerated from spec
  • cmd/api/api/instances.go — handler implementation

Test plan

  • Verify endpoint returns immediately when instance is already in target state
  • Verify timeout behavior (returns timed_out: true with current state)
  • Verify early return on terminal state (Stopped) and error state (Unknown)
  • Verify 404 when instance is deleted during wait
  • Verify client disconnect is handled gracefully
  • Verify invalid state/timeout params return 400
  • Integration test with a real instance lifecycle (create → wait for Running)

Note

Medium Risk
Adds a new blocking API endpoint and introduces state-change subscriptions into the instances.Manager interface, which affects all manager implementations and state transition paths. Risk is mainly around missed notifications, channel lifecycle on delete, and potential regressions in lifecycle methods now emitting events.

Overview
Adds a new GET /instances/{id}/wait endpoint that blocks until an instance reaches a requested state (with validated target states, configurable timeout capped at 5m, and timed_out/state_error in the response).

Implements instances.WaitForState, backed by a new per-instance subscription mechanism (Manager.Subscribe, subscribe.go) with a 5s polling fallback, and wires lifecycle operations (StartInstance, StopInstance, StandbyInstance, Restore*) to broadcast state changes and close subscribers on delete.

Updates OpenAPI/client codegen (openapi.yaml, lib/oapi/oapi.go), scopes mapping, Stainless config, and adds unit/integration tests covering immediate return, invalid params, deletion during wait, context cancellation, and subscription behavior.

Written by Cursor Bugbot for commit b0d3836. This will update automatically on new commits. Configure here.

Adds GET /instances/{id}/wait that blocks until an instance reaches
a target state, the timeout expires, or a terminal/error state is
detected. This avoids client-side polling when waiting for state
changes like waiting for an instance to become Running.

- Polls internally every 1s using existing GetInstance state derivation
- Default timeout 60s, max 5m, configurable via query parameter
- Returns immediately on Stopped (terminal) or Unknown (error) states
- Handles client disconnects via context cancellation
- Returns current state + timed_out flag in response

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Mar 25, 2026

Copy link
Copy Markdown

✱ Stainless preview builds

This PR will update the hypeman SDKs with the following commit message.

feat: Add waitForState endpoint for blocking state transitions
hypeman-openapi studio · code

Your SDK build had at least one "note" diagnostic.
generate ✅

hypeman-go studio · code

Your SDK build had at least one "note" diagnostic.
generate ✅build ⏭️lint ✅test ✅

go get github.com/stainless-sdks/hypeman-go@d6cea176033845f13c0dcbe79c0a8150c5a87d94
⚠️ hypeman-typescript studio · code

Your SDK build had at least one "error" diagnostic.
generate ❗build ✅lint ✅test ✅

npm install https://pkg.stainless.com/s/hypeman-typescript/8480bcf5d69de2b0ec82891c57386fddd049f497/dist.tar.gz

This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-03-27 17:35:08 UTC

@hiroTamada hiroTamada requested a review from sjmiller609 March 25, 2026 17:31
@hiroTamada hiroTamada marked this pull request as ready for review March 25, 2026 17:31
Comment thread cmd/api/api/instances.go Outdated
Comment thread cmd/api/api/instances.go Outdated
hiroTamada and others added 2 commits March 25, 2026 17:40
Tests cover: already-in-target-state, terminal state early return,
invalid/negative timeout validation, missing resolved instance (500),
instance deleted during wait (404), context cancellation, timeout
capping, and default timeout behavior.

All tests use fake middleware contexts to avoid Docker Hub / KVM
dependencies while still exercising the handler logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GET /instances/{id}/wait endpoint was missing from the scope
mapping, causing TestAllRoutesHaveScopes to fail in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread cmd/api/api/instances.go Outdated
hiroTamada and others added 7 commits March 25, 2026 17:59
Extracts the poll loop, terminal/error state detection, and timeout
handling from the HTTP handler into a reusable WaitForState function.
The handler now only parses request params and maps the result to
HTTP responses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Standby as terminal state for early return (alongside Stopped)
- Add initial terminal/error state check before entering poll loop
- Include StateError in context cancellation return path
- Extract isTerminalForWait helper for consistent terminal detection
- Add StandbyTerminalEarlyReturn test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the timeout timer fires, after fetching the latest state, check
whether it actually matches the target before reporting TimedOut: true.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/wait.go
hiroTamada and others added 3 commits March 25, 2026 18:46
…back

Add a subscription mechanism (subscribe.go) that lets WaitForState receive
state changes via channels instead of relying solely on polling. Manager
methods (create, start, stop, standby, restore, fork, delete) now broadcast
state changes to subscribers via notifyStateChange. WaitForState uses a
3-way select: subscription channel, 5s polling fallback (with warning log
if it detects a missed event), and context cancellation/timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove incorrect notifyStateChange(id, forked) in ForkInstance that
passed the fork result as the source instance state. Add trySend helper
with deferred recover to prevent panic if CloseAll closes a channel
between Notify's copy and send.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/wait.go
Derived state transitions (e.g. Initializing→Running via boot markers)
are not broadcast via subscription since they happen inside GetInstance.
The polling fallback detecting these is expected, not a missed event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/wait.go
Move Subscribe() call before initial state checks in WaitForState to
close the TOCTOU race between checking state and subscribing.

Remove notifyStateChange calls from CreateInstance, ForkInstance, and
ForkSnapshot — newly created instance IDs have no subscribers, so these
notifications were no-ops.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/manager.go Outdated
Comment thread lib/instances/manager.go Outdated
Follow the existing convention where instance method names match the
URL suffix (standby, restore, fork, start, stop, logs, stat, stats).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/wait.go
Paused requires explicit user action (resume/shutdown/standby) to
progress, so WaitForState should return early instead of polling
until timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/wait.go Outdated
Comment thread lib/instances/manager.go Outdated
Add TestWaitForState_Integration: a real integration test that creates
an nginx instance and uses WaitForState (subscription + polling fallback)
to wait for it to reach Running, proving the mechanism works end-to-end.

Also add Paused to isTerminalForWait since it requires explicit user
action to progress, with a unit test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread lib/instances/wait.go
hiroTamada and others added 2 commits March 25, 2026 20:37
Shutdown requires explicit user action (StopInstance or restart) to progress,
so WaitForState should return early instead of polling until timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Map the GET /instances/{id}/wait endpoint as instances.wait_for_state
and add WaitForStateResponse as a model in the instances resource.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread lib/instances/wait.go
Comment thread lib/instances/manager_test.go
@hiroTamada hiroTamada merged commit 4748a40 into main Mar 27, 2026
6 checks passed
@hiroTamada hiroTamada deleted the hypeship/add-wait-for-state branch March 27, 2026 17:33
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