Add a retry action for partial publications, and surface real API errors - #14
Merged
Conversation
The validator fetched the ISO 639-1 list from a raw gist that GitHub now rate-limits, so validation silently degraded to a format-only check on every input. The list is a static standard, so it ships with the app instead. Enables resolveJsonModule so the list can be imported directly.
Every failed call was reported as "Failed to connect to Content Alchemist API", whatever went wrong. That hid a real rejection - content-alchemist answering 400 because it could not validate a language code - behind a connection error, and a rejected request needs a different fix than an unreachable service. The thrown message is now surfaced, prefixed with the service name, and the generic wording is kept only for errors that carry no message.
A message run marks its repository as posted as soon as one integration succeeds, so the integrations that failed can never recover the item on a later run and the dashboard offered no way to finish the job by hand. Failed and partial message runs now carry a retry action that re-sends the item to the integrations named in the run details, with a confirm dialog, a loading toast that resolves into the per-integration result, and a history refresh afterwards. Runs recorded before Content Maestro tracked those details fall back to parsing their output, and the dialog then names the repository that would be used so an intervening cron run cannot come as a surprise.
Deploying content-sentinel with
|
| Latest commit: |
75a099b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e176f3b5.content-sentinel.pages.dev |
| Branch Preview URL: | https://feat-retry-failed-publicatio.content-sentinel.pages.dev |
The retry action appeared on any message run that was not fully successful, but a run can fail without ever resolving an item: an empty queue, or Content Alchemist rejecting every request, makes the job report every integration as failed while recording no repository. Retrying such a run fell back to "the latest published repository" and re-published it to every connector - a public, irreversible duplicate. A run is now retryable only when both halves are known: the integrations that missed the item and the item itself. Legacy runs recorded before the details field are still covered - the older output wording carries the repository, and where it does not the url is resolved and pinned into the request rather than left to the backend, so the confirm dialog names exactly what will be published. Also in this path: - the legacy parser no longer stops at the first period, so integration names containing one survive, the oldest output format is recognised, and "Failed:" appearing inside an error message is no longer read as a list of integrations. - the history refresh moved out of the publish try block: its failure used to overwrite the success toast with a fetch error, inviting a second click. - a row whose retry succeeded stops offering the action, since history rows are immutable and keep looking unhandled. - refresh reads the live filter state through a ref, so a slow retry cannot drag the user back to the page they started on. - the loading skeleton has the same column count as the table it stands in for.
A rejected fetch surfaces as "TypeError: Failed to fetch", which said less than the generic message it replaced. That case now keeps the plain "failed to connect" wording; messages the API actually sent are still shown.
Adds vitest and a table covering what may and may not be retried: runs with structured details, runs that resolved no item, successful and non-message runs, and the three output formats Content Maestro has used over time. This parser decides whether a button can publish to production, and every bug found in review was in exactly these branches.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
When a message run reaches some integrations but not others, the repository is already marked as posted, so it drops out of the publication queue and the integrations that failed never receive it. The dashboard showed the partial run and offered nothing to do about it.
At the same time every Content Alchemist failure was reported as
Failed to connect to Content Alchemist API. That wording hid a real rejection: the service was answering400because it could not validate a language code (see think-root/content-alchemist#6), which reads as a service outage rather than a rejected request.What changed
Three commits:
fix(language)— the ISO 639-1 list is bundled instead of fetched from a raw gist that GitHub rate-limits. The old code silently degraded to a format-only check whenever that fetch failed, soxxpassed validation. NeedsresolveJsonModuleintsconfig.app.json.fix(errors)— the thrown message is surfaced, prefixed with the service name; the generic wording remains only for errors that carry no message.feat(cron)— failed and partial message runs get a retry action in the Cron History (desktop table and mobile cards), backed by the newPOST /api/message/retryin Content Maestro (Add a manual retry for partial publications, and stop mislabelling rejections as an empty queue content-maestro#2).Behaviour of the retry, and the reasoning worth reviewing:
messageruns that are not fully successful and whose failed integrations are known. Successful runs and collect runs show nothing.details.failedon the history record. Runs recorded before Content Maestro tracked details fall back to parsingFailed: …out of the output — that parser targets the legacy wording only, since anything newer carries the structured field.refreshonuseCronJobHistory, which re-reads the current page with the active filters.Testing
npx tsc -breports no new errors,npm run buildis clean.Send https://github.com/resemble-ai/chatterbox to threads?), confirming publishes and swaps the loading toast forSent to threads, and the new manual run appears at the top of the history without a manual reload. Against a Content Alchemist that rejects requests, the toast reports the real rejection reason.Notes
TS2367errors inuseRepositories.tsare unrelated to this branch and left alone.eslintcurrently cannot run in this repo (no-unused-expressionsrule fails to load, a plugin/version conflict) and there is nolintscript, so type-check and build were used as the gate.