Platform-assigned Executa versions conflict with local versions during `anna-app apps publish`, and failed retries quickly hit the upload rate limit

Summary

While publishing an App with multiple bundled Executas to the Anna staging environment using anna-app apps publish, I encountered two related issues:

  1. The platform appears to maintain an Executa version that increments with each publish attempt. This does not appear to be the same version sequence as the version declared in the local manifest.json. Once these two sequences diverge, a normally incremented local version can collide with a version that the platform has already created or frozen.
  2. Rerunning the command after a failed publish appears to create additional upload sessions. After only a few retries, the 10/hour upload rate limit is reached, preventing further investigation or recovery.

This is more than a display issue. My current hypothesis is that each apps publish attempt may increment a platform-side version, while the manifest version of an individual bundled Executa does not change unless the developer updates it locally. The platform version can therefore move ahead of the local manifest version. When the local version is later incremented to a number that the platform has already occupied or frozen, the upload may fail with BINARY_VERSION_IMMUTABLE. Repeated attempts to resolve that failure can then trigger UPLOAD_RATE_LIMITED.

Environment

  • Anna environment: https://staging.anna.partners
  • Anna App CLI: v0.1.34
  • Publish command: anna-app apps publish
  • Local platform: macOS
  • App slug: anna-presenton
  • The App contains three bundled Executas:
    • ppt-engine
    • ppt-gener
    • anna-search
  • Example Tool ID used in this report: tool-lightvoss-anna-search-7ym3jyqv

The project defines versions as follows:

  • The App version is declared by the version field in ppt-app/app.json.
  • Each Executa’s publish version is declared in its executa.json and kept in sync with the version in the corresponding manifest.json.
  • The App references these Executas through bundled:<handle> identifiers, such as bundled:anna-search.

When the issue first occurred, the locally declared version and binary artifact version for anna-search were both 0.1.1. I later had to bump the local version several times while investigating, so the current repository version may differ from the version used in this reproduction.

Issue 1: The platform-assigned version and local manifest version form two conflicting version sequences

Using anna-search as an example, its initial local manifest version and binary build version were both 0.1.1. After several App publish attempts, however, the platform UI showed a sequence of incrementing version records. The attached screenshot shows all of the following at the same time:

  • The version list on the left contains v0.1.2, v0.1.3, and v0.1.4.
  • With v0.1.2 selected, the Executa metadata on the right shows version: v0.1.2.
  • In the Distribution section of the same record, the package name and storage URLs still contain v0.1.1.

In other words, the same Executa record exposes at least two version values: the platform record version is v0.1.2, while the local manifest and binary package version is v0.1.1.

Based on how these versions changed across repeated publish attempts, I suspect that apps publish automatically increments a platform-side Executa version on every attempt, even when the bundled Executa’s local manifest version has not changed or when the overall App publish eventually fails. If so:

  • The platform version continues to increase with each publish attempt.
  • The local Executa version increases only when the developer updates manifest.json and executa.json.
  • The platform version can move ahead of the local version and occupy version numbers that the local project may use later.
  • When the local version is later bumped to a number that the platform has already occupied or frozen, an immutable-version conflict can occur even though this is the first time the local project has published that version’s content.

I cannot yet confirm whether the platform version is generated by the server, rewritten by the App publishing flow, or derived from another field. However, the platform UI and binary URLs indicate that two inconsistent version values are being retained and used at the same time.

Actual result

Later in the investigation, I bumped both the local anna-search manifest.json and executa.json versions to 0.1.4. The anna-search@0.1.4 value in the CLI output correctly matched the local declaration at that point. However, the platform already had a frozen 0.1.4 version for the same Executa, so the upload failed with the following error:

publishing bundled executa "ppt-engine" (executas/ppt-engine)…
✓ bundled:ppt-engine → tool-lightvoss-ppt-engine-kqhra9hy (v4.0.6)
publishing bundled executa "ppt-gener" (executas/ppt-gener)…
✓ bundled:ppt-gener → tool-lightvoss-ppt-gener-2r765c57 (v3.1.3)
publishing bundled executa "anna-search" (executas/anna-search)…
✗ binary upload failed (409 BINARY_VERSION_IMMUTABLE): tool-lightvoss-anna-search-7ym3jyqv@0.1.4 (darwin-arm64) is already frozen into a published app with different content — bump the executa version and re-upload
✗ failed to publish bundled executa "anna-search" (executas/anna-search)

The problem here is not that the CLI incorrectly read 0.1.4 during this publish attempt. 0.1.4 was the local manifest version at the time. The underlying issue is that, before the local version was raised to 0.1.4, the platform’s incrementing version appears to have already reached and occupied 0.1.4. As a result, a normal local version bump still collided with an immutable version that already existed on the platform.

This raises the following questions:

  1. Does the platform-side version shown in the UI increment on every apps publish attempt, regardless of whether the local manifest version changed?
  2. If the overall App publish fails, does the platform still create and freeze a new Executa version?
  3. How are the platform-assigned version and local manifest version used for records, uploads, and immutability checks? Why do they share the same version-number namespace?
  4. If the local manifest version is lower than the platform’s incrementing version, how should a developer select the next version that will not conflict?

Expected result

  • The platform should clearly distinguish between the platform-assigned version and the local manifest version. If they represent the same Executa release version, there should be one authoritative source instead of two independent sequences.
  • If versions must be assigned by the server, the documentation and CLI output should explain the mapping clearly, and server-assigned versions should not occupy the namespace used by local manifest versions.
  • If the version is intended to come from executa.json / manifest.json, the platform version list, metadata, and immutability checks should use that version consistently and should not silently increment it based on publish attempts.
  • A failed App publish should not create or freeze an Executa version that was never successfully published. At minimum, it should not prevent a developer from later using the same version number for the first valid publish of that local version.
  • For conflicts with an already frozen version, the CLI should ideally perform a preflight check before creating an upload session or uploading a binary. The error should show the local declared version, the server-resolved version, and the existing version being compared.

Issue 2: Failed retries quickly exhaust the upload-session limit

After encountering the immutable-version conflict, I updated the configuration or version and reran anna-app apps publish. Because the App contains three bundled Executas, each invocation starts again with the first Executa. After several failed attempts, the following error appeared:

publishing bundled executa "ppt-engine" (executas/ppt-engine)…
✗ binary upload failed (429 UPLOAD_RATE_LIMITED): too many upload sessions — limit 10/hour
✗ failed to publish bundled executa "ppt-engine" (executas/ppt-engine)

The CLI reports only a 10/hour limit. It does not explain:

  • What exactly counts as one upload session: one command invocation, one Executa, one target platform, or one binary artifact?
  • Whether a session still counts against the limit when the upload fails during a version check or another step.
  • Whether repeated uploads with the same content can reuse an existing result based on the content hash instead of creating another session.
  • Whether the limit is scoped to the user, PAT, App, Tool ID, or environment.
  • How much quota remains and exactly when it resets.

An App publish may process several Executas sequentially. If a later Executa fails, the next retry processes the earlier Executas again. This makes a 10/hour limit very easy to exhaust during investigation. Even when a developer is only trying to resolve one publish failure, a small number of retries can block further work for an hour.

Expected result

  • Errors that can be detected before upload, such as version conflicts or manifest validation failures, should not create or consume binary upload sessions.
  • When an Executa with the same content hash has already uploaded successfully, retrying the App publish should reuse that result.
  • If an App publish fails partway through, it should be possible to resume from the failed Executa instead of re-uploading earlier Executas whose content has not changed.
  • A 429 response should include and display the quota scope, current usage, reset time, or Retry-After value.
  • If apps push followed by apps cut is the only recommended flow and avoids these issues, the documentation should clearly explain how it differs from apps publish in version assignment, freeze timing, retry idempotency, and upload quotas. As long as apps publish remains available, it should either remain reliably usable or provide actionable migration guidance.

Steps to reproduce

  1. Prepare an App containing multiple bundled Executas, each configured with local binary artifacts.
  2. Declare the same version in an Executa’s executa.json and manifest.json, then build binary artifacts containing that version number.
  3. Keep one Executa’s local manifest version unchanged and run anna-app apps publish several times. A later bundled Executa can be made to fail so that the full App publish flow must be retried.
  4. After each attempt, inspect the Executa version list in the platform UI. Check whether the platform version continues to increment while the local manifest version remains unchanged, and whether failed App publishes also create new versions.
  5. Compare the version metadata, Distribution URL, local manifest version, and binary filename for the same platform record.
  6. Bump the local manifest version to a version number that the platform has already reached or occupied, rebuild the binary, and publish again.
  7. Check whether the platform returns BINARY_VERSION_IMMUTABLE, stating that the local version is already frozen with different content.
  8. Continue adjusting or bumping the version and retrying. After several publish attempts, check whether UPLOAD_RATE_LIMITED: limit 10/hour is returned.

Impact

  • The platform-assigned version and local manifest version exist in parallel, so developers cannot reliably determine which Executa version to use for the next publish.
  • Repeatedly bumping versions to work around platform-side conflicts creates many meaningless immutable version records.
  • Retrying a failed publish is not idempotent and repeatedly uploads Executas whose content has not changed.
  • The upload-session limit provides no usage or recovery information. Once the limit is reached, investigation must stop, significantly increasing the feedback cycle.
  • Inconsistent versions across the platform version list, metadata, and actual binary package make rollback, auditing, and debugging more difficult.

Questions for the platform team

  1. For bundled Executas, which field determines the final published version in the apps publish, apps push, and apps cut flows? Does the server increment a version on every publish attempt, including attempts that ultimately fail?
  2. In the screenshot, the platform record and metadata show v0.1.2, while the Distribution package name and URL show v0.1.1. Is this expected behavior?
  3. Can the BINARY_VERSION_IMMUTABLE preflight check happen before upload and return a detailed comparison of the local and server-side versions?
  4. How is the 10/hour upload-session limit counted? Do failed or duplicate uploads count, what is the quota scope, and when does it reset?
  5. Can publishing an App with multiple Executas support content-hash deduplication, reuse of successful steps, and resumable retries?

I can provide the complete CLI logs, the corresponding versions of app.json, executa.json, and manifest.json, binary filenames and SHA-256 hashes, and the platform screenshot if more information is needed for investigation.

In addition, the limit did not appear to reset after one hour. I waited for more than 60 minutes after first receiving the UPLOAD_RATE_LIMITED response, then attempted the upload again, but the CLI returned the same error. This suggests that 10/hour may not mean a simple fixed one-hour cooldown from the first rejection, or that upload sessions remain active for longer than the message indicates. Without a reset timestamp or Retry-After value, there is no reliable way to determine when publishing can resume.

Hi @HappyLight :waving_hand:

Thank you so much for this outstanding report! :folded_hands: The level of detail — the version timelines, screenshots, error output, and your hypothesis about two diverging version sequences — made this a joy to investigate. And your analysis was spot on. :bullseye:

We’ve confirmed both issues and both are now fixed. Here’s a summary:

Issue 1: Platform-assigned versions diverging from local manifest versions :white_check_mark: Fixed

You were exactly right. Two things were compounding:

  1. Server side — the publish flow could auto-increment a platform-side Executa version on publish attempts even when your local manifest.json version hadn’t changed. This let the platform sequence race ahead and silently occupy version numbers your project would later want to use — leading to the BINARY_VERSION_IMMUTABLE collision you hit.
  2. CLI side — the CLI only synced your local version to the platform at first registration, so subsequent local bumps weren’t always reflected server-side.

What changed:

  • :hammer_and_wrench: The platform no longer invents versions. Your executa.json / manifest.json version is now the single source of truth for apps publish and apps cut. If there’s a genuine conflict with an already-frozen version, you’ll get a clear, actionable error up front telling you to bump — never a silent auto-increment.
  • :hammer_and_wrench: The CLI now re-syncs your declared version on every publish/push.

Fixed in: platform release 1.1.0-beta.90 and CLI v0.1.37 — please upgrade from v0.1.34:

npm i -g @anna-ai/cli@latest

Issue 2: Failed retries exhausting the upload rate limit :white_check_mark: Fixed

This one was also very real — and your instinct that failed attempts shouldn’t burn quota was correct. We’ve overhauled the upload-session accounting:

  • :vertical_traffic_light: Version conflicts are now caught in a preflight check before any upload session is created — a BINARY_VERSION_IMMUTABLE conflict no longer consumes quota.
  • :recycling_symbol: Retries with unchanged content reuse existing results (content-hash dedup / resume) and are quota-free — only genuinely new upload sessions count toward the limit.
  • :stopwatch: Rejected attempts no longer extend the rate-limit window (previously a burst of retries could keep you locked out much longer than intended — sorry about that! :sweat_smile:).
  • :clipboard: 429 responses now include a Retry-After header and remaining-quota info, so you’ll know exactly when you can retry.

What you should do :rocket:

  1. Upgrade the CLI: npm i -g @anna-ai/cli@latest (≥ v0.1.37)
  2. Bump your affected Executa versions (e.g. anna-search) past any occupied numbers one last time — the stray frozen versions from before the fix are immutable, but the sequence will no longer drift on its own.
  3. Publish as usual — versions will now track your manifest.json exactly.

Thanks again for pushing on this — reports like yours directly make the platform better for every developer. If you hit anything else after upgrading, we’re all ears! :yellow_heart: