fix(api): backup producers drop config and crash uploads with multiline metadata#174
Open
bchoor wants to merge 2 commits into
Open
fix(api): backup producers drop config and crash uploads with multiline metadata#174bchoor wants to merge 2 commits into
bchoor wants to merge 2 commits into
Conversation
producerOpts only forwarded sourceIds/command/exclude from policy.payloadConfig, silently dropping producer-specific keys like produceCommand/restoreCommand/artifactName. Every custom_command backup policy (including all mail-server backups) failed with "custom_command producer requires `produceCommand` in policy payload config".
destination.put() forwarded artifact.metadata straight into the S3 put call, where every key becomes an x-amz-meta-* HTTP header. Header values must be printable ASCII with no newlines, but custom_command artifacts store multiline produceCommand/ restoreCommand text in metadata, so the upload died with an invalid header error. Filter to header-safe string values before the put; the unfiltered metadata is still recorded on the run for restore.
bchoor
marked this pull request as ready for review
July 23, 2026 22:31
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.
This PR fixes two distinct bugs in the backup pipeline that stack on top of each other:
custom_commandbackups first fail because the orchestrator drops the producer's config keys (Bug 1), and once that is fixed they fail again because the artifact's multiline metadata is sent as illegal S3 object-metadata headers (Bug 2). Both fixes are needed before acustom_commandbackup — which is what every mail-server backup policy is — can complete. One commit per bug.Bug 1:
produceCommanddropped frompayloadConfigEvery backup policy using the
custom_commandproducer fails at run time with:BackupOrchestratorbuilds the options passed to the producer as an allowlist of exactly three keys —sourceIds,command,exclude— pulled offpolicy.payloadConfig:Any other field stored in
payloadConfig— notablyproduceCommand,restoreCommand, andartifactName, which thecustom_commandproducer requires — is silently dropped before it ever reachesproducer.produce(...). Since acustom_commandpolicy's entire configuration lives in those producer-specific keys, this allowlist breaks the producer outright. In practice this means every backup policy of kindcustom_commandfails, which includes all mail-server backup policies (they are configured exclusively ascustom_command).Fix 1
Spread the full
policy.payloadConfigintoproducerOptsfirst, before the three explicit keys. The explicitsourceIds/command/excludeassignments are left in place unchanged, so any policy that only relies on those keys keeps behaving exactly as before — the spread only adds back the keys that were previously being thrown away.Bug 2: artifact metadata crashes the upload with an invalid header
This second bug is only reachable once Bug 1 is fixed and a
custom_commandproducer actually runs. The producer's artifact carries itsproduceCommand/restoreCommandtext inartifact.metadata(needed later for restore).uploadArtifactforwards that metadata object as-is into the object-storeputcall:For an S3-compatible destination, each metadata key becomes an
x-amz-meta-*HTTP header, and HTTP header values must be printable ASCII with no newlines.produceCommand/restoreCommandare multiline shell scripts, so the upload dies with:Fix 2
Filter the metadata passed to the object-store
putcall down to header-safe values only (typeof v === "string"and printable-ASCII-only), and pass that filtered object asmetadatainstead of the raw one. The unfilteredartifact.metadatais unaffected elsewhere: it is still recorded verbatim on the backup run's DB record, which is what the restore path readsrestoreCommandfrom — restore never reads object-store headers, so this filtering has no effect on restore behavior.Verification
On a self-hosted v0.3.0 instance managing a mail server, a
custom_commandbackup policy was re-run after each fix in turn:produceCommanderror above.snapshotting→uploading→verifying, its artifact was uploaded to an S3-compatible backup destination, and the run's manifest was written alongside it.The run history telling the whole story — bottom to top: the Bug 1 failure (
produceCommanderror), the Bug 2 failure after Fix 1 alone (metadata header error), and the same policy succeeding after both fixes:🤖 Generated with Claude Code