Skip to content

fix(sdk): make the 0.2.0 SDK release safe to publish - #6616

Merged
waleedlatif1 merged 1 commit into
stagingfrom
fix/sdk-release-correctness
Aug 12, 2026
Merged

fix(sdk): make the 0.2.0 SDK release safe to publish#6616
waleedlatif1 merged 1 commit into
stagingfrom
fix/sdk-release-correctness

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Three release-correctness defects in the two SDK packages, all of which would have shipped badly on the next publish.

1. The TypeScript SDK carried five breaking changes but was versioned 0.1.3.

The v2 migration already landed on this branch's base. It changed, in the published API:

  • executeWorkflow moved to POST /api/v2/workflows/{id}/execute, with the workflow input nested under input and async / executionTimeoutSeconds moved from the X-Execution-Mode / X-Execution-Timeout-Seconds headers into the body
  • AsyncExecutionResult.jobId renamed to runId
  • executionId removed from AsyncExecutionResult
  • getJobStatus superseded by getWorkflowRun(workflowId, runId)
  • a failed synchronous run now throws instead of resolving with { success: false }

At 0.1.3 every existing consumer would have taken all five automatically: npm expands ^0.1.2 to >=0.1.2 <0.2.0-0, so 0.1.3 is in range and 0.2.0 is not. Verified with the resolver rather than by eye:

semver.satisfies('0.1.3', '^0.1.2') === true
semver.satisfies('0.2.0', '^0.1.2') === false

Now 0.2.0. Minor rather than major because the package is pre-1.0 and 0.x minors are where breaking changes belong under semver — 1.0.0 would additionally assert a stability commitment the package has not made. 0.1.3 was never published (npm has 0.1.0, 0.1.1, 0.1.2), so nothing is being reused or overwritten.

2. The Python SDK was rewritten onto /api/v2 with no version bump, so it was silently unpublishable.

pyproject.toml still said 0.1.2 and PyPI already has 0.1.2. The publish job's existence check therefore matched, the job skipped, and it skipped green — so the repo and PyPI diverged with no failing signal. Every v2 change made since has been sitting unpublished. Now 0.2.0.

3. A cancelled run reported success=True in Python and False in TypeScript.

Python derived success = status != 'failed', which counts cancelled as a success. TypeScript derives status === 'completed' || status === 'paused'. Python was the wrong side and now matches. The predicate is a whitelist on purpose: a status added to the API later defaults to "not successful" rather than silently reporting True.

A new WorkflowExecutionResult.status field carries the server's terminal status verbatim, so a caller can tell a cancelled run from a failed one — both report success=False. The two SDKs now agree on the predicate for all four terminal states and differ only in delivery, which is documented on both sides:

statusTypeScriptPython
completedsuccess: truesuccess=True
pausedsuccess: truesuccess=True
cancelledsuccess: falsesuccess=False
failedthrows SimStudioErrorreturns success=False, status='failed', error set

Also fixed, found while verifying the above: Python parsed X-RateLimit-Reset with a bare int(). The v2 API sends that header as an ISO 8601 timestamp (resetAt.toISOString()), so everyexecute_workflow call against v2 would have raised ValueError out of the rate-limit bookkeeping — the Python 0.2.0 would have been dead on arrival. It now mirrors the TypeScript parser exactly (all-digit kept as-is, otherwise parsed to epoch ms, otherwise 0) and degrades rather than raising, because a quota hint must never take down the call it rode in on.

Both READMEs get a migration guide. A breaking major without one is the actual defect.

Publish implications — read before merging

This is a breaking release of two packages to two registries that cannot be un-published cleanly. Versions can never be reused.

  • Nothing publishes on merge to staging. Both publish-ts-sdk.yml and publish-python-sdk.yml trigger on push to main only, path-filtered to their package. The publish fires when staging is promoted to main, not on this merge.
  • Each job reads exactly one version file. The npm job reads packages/ts-sdk/package.json; the PyPI job reads [project].version from packages/python-sdk/pyproject.toml. Both are 0.2.0 here, and neither version exists on its registry, so both existence checks miss and both will publish. Each also cuts a GitHub release tag.
  • packages/python-sdk/setup.py still says 0.1.1 and is dead. The publish job does not read it, and because [project].version is static, setuptools ignores the version= kwarg passed to setup(). It does not affect this release. Left alone deliberately — see follow-ups.
  • Consumers are not auto-upgraded. That is the entire point of 0.2.0: ^0.1.2 and simstudio-sdk<0.2 both stay on the old line, and upgrading becomes a deliberate act.

Server compatibility claim

The branch originally asserted a minimum server version of "Sim v0.7.69 or later" in both READMEs. I removed that number rather than shipping it, because a published README cannot be corrected without cutting another release and the claim did not hold up:

  • No stable v0.7.69 tag exists — only -beta / -staging prereleases. The latest stable tag is v0.7.68.
  • The commit that added the v2 execute route (improvement(external-endpoints): v2 versions with clean signatures + updated docs based on openapi spec #5273) is on staging and not on main, so v0.7.68 does not contain it and a stable v0.7.69 cut before that promotion would not either.
  • More fundamentally, a version number cannot express the requirement at all. The whole /api/v2 surface sits behind the v2-api gate, which answers 404 when off. A self-hosted deployment serves it only when the operator enables V2_API, regardless of which version it runs.

The READMEs now describe the requirement in terms the reader can actually check — the endpoint, the V2_API setting, and the 404 they will see — with no version number to go stale.

Type of Change

  • Bug fix

Testing

  • packages/python-sdk: 40 passed. Run with PYTHONPATH=. python -m pytest tests/test_client.py -q. Note that PR CI does not cover this suite — the Python SDK is not a JS workspace, so turbo run test skips it and the tests run only inside the publish job on main. Run locally when reviewing.
  • packages/ts-sdk: 38 passed (bunx vitest run src/index.test.ts). This one is covered by PR CI via turbo run test.
  • Re-proved the cancelled-run test fails without the fix: reverting success to status != 'failed' turns test_sync_execution_cancelled_is_not_success red, and restoring it turns it green with a clean tree. Same for the ISO reset parser — reverting to int(reset) fails three tests with ValueError.
  • bun run type-check and biome clean on the changed TypeScript.
  • bun.lock changed by exactly one line: the packages/ts-sdk workspace version propagating. No dependency, resolution, or integrity change.

Explicitly out of scope

A 404-fallback / dual-endpoint compatibility shim was considered and rejected. The legacy 202 body's statusUrl points at /api/jobs/{jobId}, so mapping jobId onto runId would hand the caller a runId that this SDK's own getWorkflowRun cannot resolve — a silently broken poll is worse than an honest 404.

Follow-ups, not this PR

  • RateLimitInfo.reset carries epoch seconds for the legacy integer header and epoch milliseconds for the ISO one, in both SDKs. That inconsistency is faithfully mirrored from TypeScript rather than fixed here; normalizing both to milliseconds is a separate breaking change to a separate field.
  • The non-English SDK doc pages still document the 0.1.x API (job_id / jobId / get_job_status / getJobStatus) and become wrong the moment 0.2.0 publishes. This is broader than it first looks: 20 pages across 5 locales{de,es,fr,ja,zh} × {api-reference,sdks} × {python,typescript}. The English api-reference pages are already current. Separately, there is no en/sdks/ directory at all, so the ten */sdks/*.mdx pages are orphaned translations of a source page that no longer exists and no English-side edit will ever propagate to them.
  • packages/python-sdk/setup.py should probably be deleted outright rather than bumped. It is a wholly redundant second metadata declaration — name, description, classifiers, install_requires, python_requires all duplicated from pyproject.toml and none of it consulted. Bumping it to 0.2.0 would make it look live and guarantee the same drift recurs at 0.3.0. Deleting it removes the trap: if version were ever moved to dynamic, setup.py's stale value would silently become authoritative.
  • The TypeScript WorkflowExecutionResult has no status field, so "a resolved success: false means it was cancelled" is an inference the caller cannot verify from the type. It holds today only because failed throws. Python fixed exactly this; adding status to TypeScript would be another breaking-ish change and belongs on its own.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

The v2 SDK migration (#5273, #6564) shipped five breaking changes in both
SDKs but got the release mechanics wrong in three separate ways, and left
one of the two rewrites unable to complete a single successful call.
Versions. packages/ts-sdk/package.json read 0.1.3 -- a patch digit added
inside an unrelated compatibility commit, never deliberated. npm expands
^0.1.2 to >=0.1.2 <0.2.0, so every existing consumer would have picked the
break up on a lockfile refresh: AsyncExecutionResult.jobId renamed to
runId, executionId dropped from that interface, a failed sync run now
throwing instead of resolving {success:false}, the request body reshaped,
and the endpoint moved to /api/v2 with no fallback. 0.2.0 excludes every
existing range, so the upgrade becomes opt-in. packages/python-sdk carries
the identical break and was never bumped at all, so its publish job would
have skipped green at the "version already exists" gate and left the repo
and PyPI silently divergent; it moves 0.1.2 -> 0.2.0 in lockstep, along
with the __version__ string in simstudio/__init__.py, which tracks
pyproject and would otherwise have started lying. setup.py is left at
0.1.1: it is unchanged from main and demonstrably unread (0.1.2 published
from pyproject while setup.py already said 0.1.1). It wants deleting, in
its own commit.
A 404 fallback was considered and rejected. The legacy 202 body's statusUrl
points at /api/jobs/{jobId}, so mapping jobId onto runId would hand the
caller an id that getWorkflowRun cannot resolve against that same old
server -- a successful execute followed by an inexplicable failure on the
next call is a worse contract than a clean 404. Both READMEs instead state
the minimum server version and name the endpoint to check for.
Cancelled runs. packages/python-sdk computed success as status != 'failed',
so a run cancelled out of band reported success=True. The TypeScript SDK
uses a closed whitelist and reports False, and before the migration both
SDKs read the server's own value, which was False -- so this was a Python
regression, not merely an inconsistency. Fixed by mirroring the whitelist.
The v2 contract enumerates exactly completed|failed|paused|cancelled, so
narrowing the blacklist to a whitelist cannot drop a live value, and a
status added later now defaults to "not successful" rather than silently
reporting True. WorkflowExecutionResult gains a status field because
Python, unlike TypeScript, does not throw on 'failed' -- so success=False
alone is ambiguous there in a way it is not in the TypeScript SDK, which
is why status is not added to both.
Rate-limit header. Found while auditing the two SDKs for further
divergence, and the reason the Python bump could not have shipped as it
stood: every authenticated v2 response now carries X-RateLimit-Reset as an
ISO 8601 timestamp (recorded by v2RateLimits.publicApi, stamped by
withRouteHandler). The Python SDK parsed it with int(), raising a bare
ValueError that no handler in execute_workflow catches -- so every
successful v2 execution raised instead of returning. None of the legacy
endpoints the SDK previously called record a rate-limit snapshot, which is
why the latent int() survived until the v2 move. The TypeScript SDK
already branches on the format; _parse_reset_header mirrors it, including
degrading an unrecognised value to 0, because a quota hint must not take
down the call it rode in on.
Timing metadata. The v2 rewrite stopped forwarding startedAt/endedAt, which
main passed through and the TypeScript SDK still reports; restored under
the same startTime/endTime keys the TypeScript SDK uses.
Tests: cancelled/failed/paused status coverage, the ISO reset header, and
the restored metadata keys, each verified red against the unfixed line
first. The TypeScript suite gains matching cancelled/paused and ISO-reset
pins -- they pass against today's source by design, and were confirmed to
fail against a deliberately degraded copy so they are not toothless.
Deliberately not included: a CI guard failing a PR that changes SDK source
without a version bump. It would have caught this twice over, but it is a
new script and workflow rather than a fix to the defect at hand.
Review revision. bun.lock recorded packages/ts-sdk at 0.1.3 and was left
stale by the first pass, so the repo asserted two versions for the same
workspace package -- in a change whose whole thesis is that the version
strings had diverged. It does not break CI (bun 1.3.14 accepts the
mismatch under --frozen-lockfile, confirmed here), but 092311e bumped
the lock in lockstep with package.json, and the next unfrozen install
would otherwise drop the line into an unrelated PR.
_parse_reset_header gated the numeric branch on str.isdigit(), which
accepts characters int() rejects ('²'.isdigit() is True, int('²')
raises) -- and that int() sits outside the try, so the one function added
to stop a quota hint raising could still raise, contradicting its own
docstring. str.isdecimal() is exactly the set int() accepts. The
tolerates-unparseable test is parametrized over both forms and was
confirmed red on '²' against isdigit.
Docs and docstrings: apps/docs api-reference/python.mdx mirrors the
README's dataclass block and was the only copy left without the new
status field. RateLimitInfo now names its units, because reset is epoch
seconds for the legacy integer and milliseconds for the ISO form that v2
sends. execute_workflow's Args entry still described the pre-v2 body
shape ("spread at root level"); every input is nested under input now,
and this is the commit that ships that help() text to PyPI. The
"declared last so positional construction keeps working" sentence was a
maintainer's note that belongs in this message, not in every user's
help(WorkflowExecutionResult).
@waleedlatif1
waleedlatif1 requested a review from a team as a code ownerAugust 12, 2026 08:41
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
docsReadyReadyPreviewAug 12, 2026 8:47am

Request Review

@cursor

cursorBot commented Aug 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Breaking 0.2.0 publish of both SDKs plus changes to execution success semantics and rate-limit parsing; incorrect behavior here would ship to all consumers and cannot be cleanly unpublished.

Overview
Makes the 0.2.0 SDK publish safe by bumping both packages out of the 0.1.x range and fixing two Python defects that would have shipped broken.

Versioning:simstudio-sdk and simstudio-ts-sdk move to 0.2.0 so existing ^0.1.x consumers are not auto-upgraded onto the v2 API break. Both READMEs add server-compatibility notes and upgrade guides.

Python correctness:WorkflowExecutionResult.success now uses a whitelist (completed / paused) instead of status != 'failed', so cancelled runs report success=False. A new status field exposes the server terminal status. Rate-limit parsing now accepts v2's ISO X-RateLimit-Reset (and degrades to 0 on bad values) instead of crashing every execute call with ValueError. Metadata also includes startTime / endTime.

Tests cover cancelled/failed/paused outcomes and ISO vs unparseable reset headers.

Reviewed by Cursor Bugbot for commit 679049a. Configure here.

@greptile-apps

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR prepares the TypeScript and Python SDKs for their breaking 0.2.0 release and corrects Python synchronous-status and rate-limit-reset handling.

  • Bumps both published SDK package versions to 0.2.0 and synchronizes runtime and lockfile metadata used by release tooling.
  • Makes Python report only completed and paused synchronous runs as successful while exposing the terminal status to callers.
  • Parses v2 ISO 8601 rate-limit reset headers without allowing malformed timestamps to fail ordinary requests.
  • Adds migration and server-compatibility documentation plus regression coverage for terminal statuses and reset-header formats.

Confidence Score: 5/5

The PR appears safe to merge, with the release metadata, SDK behavior, documentation, and regression tests aligned.

The changed status mapping covers the complete synchronous v2 status contract, expected rate-limit reset formats degrade safely, and the authoritative package versions consumed by publishing are consistently set to 0.2.0.

Important Files Changed

FilenameOverview
packages/python-sdk/simstudio/init.pyCorrectly adds terminal status reporting, aligns success classification with the v2 contract, and safely parses expected ISO and legacy rate-limit reset formats.
packages/python-sdk/tests/test_client.pyAdds focused regression tests for completed, paused, cancelled, and failed executions and for ISO or malformed reset headers.
packages/python-sdk/pyproject.tomlAdvances the authoritative Python package version to the unpublished breaking-release boundary.
packages/ts-sdk/package.jsonAdvances the TypeScript SDK to 0.2.0 so existing 0.1.x caret ranges do not absorb breaking API changes.
packages/ts-sdk/src/index.test.tsExtends coverage for cancelled and paused status mapping and the v2 ISO reset-header format.
packages/python-sdk/README.mdDocuments v2 server requirements, migration steps, result semantics, and the intentional Python failure-delivery behavior.
packages/ts-sdk/README.mdDocuments the breaking v2 migration, server feature-gate requirement, and changed synchronous failure semantics.

Reviews (1): Last reviewed commit: "fix(sdk): make the 0.2.0 SDK release saf..." | Re-trigger Greptile

@waleedlatif1
waleedlatif1 merged commit 34d65df into stagingAug 12, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/sdk-release-correctness branch August 12, 2026 08:48
Sign up for freeto 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.

1 participant

@waleedlatif1