Skip to content

Commit bb154f8

Browse files
authored
Give the Store upload a timeout, and clear an abandoned submission (#52)
The first run of the Store stage, on 0.9.4, authenticated, found the app, created the submission, prepared the bundle, and then failed: ``` Uploading Bundle to Azure blob: 0% Error while uploading the application package. System.AggregateException: Retry failed after 6 tries. (The operation was cancelled because it exceeded the configured timeout of 0:00:00.) ``` ## It is not a network fault `msstore publish` takes `--uploadTimeout` in seconds and passes it straight through: ```csharp blobClientOptions.Retry.NetworkTimeout = TimeSpan.FromSeconds(uploadTimeout); ``` Unset, that is zero, so every attempt is cancelled the instant it starts — six times, then the aggregate. It failed identically on a developer machine, which is what ruled out the agent's network. The option is missing from the Learn documentation and present in `msstore publish --help`, which is why it was missed when the stage was written. It applies per attempt, so 900s is deliberately generous for a package of around 100 MB rather than a tight bound. ## The failure wedged the next attempt It left a created-but-empty submission behind. Every later run would then fail on the guard against touching a pending submission — one failure disabling the automation until someone opened Partner Center, which is the thing this stage exists to avoid. The stage now deletes a submission whose status is `PendingCommit`: created, never committed, nothing in certification, nothing a person published. Every other status still fails the stage untouched, because an in-flight submission may be a person's listing edit. The status parsing was checked against live CLI output, escape codes and all. ## The message that misled The old failure text asserted that a pending submission was the usual cause. It was wrong here and cost real diagnosis time. It now points at the verbose output, and `publish` runs verbose so the next failure reports its own exception instead of one swallowed line. ## What this does not do 0.9.4 was not submitted and will not be. A pipeline run uses the YAML on `main` as of that run, and `v0.9.4` cannot be released twice, so the next promoted release is what exercises this. Submitting 0.9.4 by hand would defeat the point.
1 parent ac36d2b commit bb154f8

4 files changed

Lines changed: 76 additions & 20 deletions

File tree

.azure/pipelines/build-whiteboard.yaml

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,11 @@ stages:
398398
# Public: it is the address of the listing, apps.microsoft.com/detail/9NN5N0L2TMTF.
399399
- name: storeProductId
400400
value: '9NN5N0L2TMTF'
401+
# Per upload attempt, and the package is around 100 MB, so this is generous rather than
402+
# tight - a hung upload should still fail inside the job rather than run to the timeout
403+
# of the job itself.
404+
- name: storeUploadTimeoutSeconds
405+
value: '900'
401406
jobs:
402407
- job: Submit
403408
displayName: Submit the released MSIX
@@ -464,13 +469,50 @@ stages:
464469
STORE_CLIENT_ID: $(StoreClientId)
465470
STORE_CLIENT_SECRET: $(StoreClientSecret)
466471

472+
# A failed submission leaves a created-but-empty submission behind, which would block
473+
# every later attempt - the first failure wedges the automation until someone visits
474+
# Partner Center. PendingCommit means exactly that: created, never committed, so no
475+
# certification is running and nothing a person published is at stake. Anything else
476+
# is left alone, because an in-flight submission may be someone's listing edit and a
477+
# pipeline must not discard it.
478+
- task: PowerShell@2
479+
displayName: 'Clear an abandoned submission'
480+
inputs:
481+
targetType: 'inline'
482+
pwsh: true
483+
script: |
484+
$ErrorActionPreference = 'Stop'
485+
Set-StrictMode -Version Latest
486+
487+
# The CLI colours its output, so strip the escape sequences before matching.
488+
$report = (msstore submission status $env:STORE_PRODUCT_ID 2>&1 | Out-String) -replace "`e\[[0-9;]*m", ''
489+
Write-Host $report
490+
491+
if ($report -notmatch 'Found Pending Submission') {
492+
Write-Host 'No pending submission. Nothing to clear.'
493+
return
494+
}
495+
496+
$status = if ($report -match 'Submission Status\s*=\s*(\w+)') { $Matches[1] } else { 'unknown' }
497+
if ($status -ne 'PendingCommit') {
498+
throw "A submission is pending with status '$status'. It is not an abandoned upload, so this stage will not delete it. Resolve it in Partner Center, then re-run this stage."
499+
}
500+
501+
Write-Host 'Deleting an abandoned submission left by an earlier failed upload.'
502+
msstore submission delete $env:STORE_PRODUCT_ID --no-confirm
503+
if ($LASTEXITCODE -ne 0) { throw "msstore submission delete exited with $LASTEXITCODE" }
504+
env:
505+
STORE_PRODUCT_ID: $(storeProductId)
506+
467507
# Packages only. The listing text, screenshots, and 'What's new' are carried over from
468508
# the last published submission untouched; changing them is 'msstore submission
469509
# updateMetadata' and a deliberate act, recorded in installer/msix/STORE-LISTING.md.
470510
#
471-
# This fails if a submission is already pending for the product. That is correct: an
472-
# in-flight submission may be someone's manual listing edit, and a pipeline must not
473-
# discard it. Resolve it in Partner Center, then re-run this stage.
511+
# -ut is not optional in practice. Unset, the CLI passes zero to
512+
# BlobClientOptions.Retry.NetworkTimeout, so every upload attempt is cancelled the
513+
# instant it starts and the whole publish fails with 'Retry failed after 6 tries' and
514+
# a configured timeout of 0:00:00. The Learn documentation does not mention the option
515+
# at all; 'msstore publish --help' does.
474516
- task: PowerShell@2
475517
displayName: 'Submit the package'
476518
inputs:
@@ -480,12 +522,13 @@ stages:
480522
$ErrorActionPreference = 'Stop'
481523
Set-StrictMode -Version Latest
482524
483-
msstore publish $env:MSIX_PATH -id $env:STORE_PRODUCT_ID
525+
msstore publish $env:MSIX_PATH -id $env:STORE_PRODUCT_ID --uploadTimeout $env:UPLOAD_TIMEOUT --verbose
484526
if ($LASTEXITCODE -ne 0) {
485-
throw "msstore publish exited with $LASTEXITCODE. A pending submission in Partner Center is the usual cause; the release itself is unaffected."
527+
throw "msstore publish exited with $LASTEXITCODE. Read the verbose output above for the cause; the release itself is unaffected either way."
486528
}
487529
488530
Write-Host 'Submitted. Certification takes hours to days and is tracked in Partner Center.'
489531
env:
490532
MSIX_PATH: $(MsixPath)
491533
STORE_PRODUCT_ID: $(storeProductId)
534+
UPLOAD_TIMEOUT: $(storeUploadTimeoutSeconds)

TODO.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,16 @@ visible and gates nothing.
5555

5656
## Waiting on the first automated Store submission
5757

58-
Also not work. The pipeline's Store stage has never run. The only submission so far is the
59-
manual one for 0.9.2, and the stage was written against the documented behaviour of the
60-
Microsoft Store Developer CLI rather than against a run of it. The first promoted release
61-
after this lands is the one that proves it.
62-
63-
0.9.4 is that release. `VersionPrefix` was bumped for it, because the Release stage
64-
refuses to reuse an existing tag and 0.9.3 could not be released a second time to carry
65-
the test.
58+
Also not work. The stage ran for the first time on 0.9.4 and failed: it authenticated,
59+
created the submission, and then could not upload the package, because `msstore publish`
60+
defaults its blob upload timeout to zero when `--uploadTimeout` is not given. That is
61+
fixed, but the fix has not been exercised — a pipeline run uses the YAML on `main` at the
62+
time it runs, and 0.9.4 cannot be released twice, so the next promoted release is the one
63+
that proves it.
64+
65+
0.9.4 was not submitted to the Store. Nothing depends on the Store carrying every version,
66+
and the alternative was submitting it by hand, which is the thing this stage exists to
67+
avoid.
6668

6769
Watch two things on that run. The stage has to pick the MSIX out of `drop-x64-true`, since
6870
both matrix jobs pack an identically named package and only one of them is self-contained.

docs/decisions.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@ incomplete listing.
217217

218218
Everything after it is the Store stage's job. It submits packages only — listing text,
219219
screenshots, and **What's new** carry over from the last published submission untouched.
220-
Changing them stays a deliberate act in Partner Center, recorded in `STORE-LISTING.md`, for
221-
the same reason the pipeline does not delete a pending submission to make room for its own:
222-
an in-flight submission may be a person's listing edit, and a pipeline must not discard it.
220+
Changing them stays a deliberate act in Partner Center, recorded in `STORE-LISTING.md`.
221+
The stage is equally careful with submissions it did not create: it deletes only one left
222+
`PendingCommit` by its own failed upload, because any other pending submission may be a
223+
person's listing edit and a pipeline must not discard it.
223224

224225
The submission identity is not the signing one. The Partner Center account is associated
225226
with a different Microsoft Entra tenant than the one the pipeline signs in, so the two

docs/release-management.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,14 @@ already published. Certification takes hours to days and gates nothing (decision
299299

300300
`UseMSStoreCLI@0` installs the [Microsoft Store Developer CLI][msstore]; `msstore
301301
reconfigure` authenticates with the `SQLBI-StoreSubmission` group, and `msstore publish`
302-
uploads the package against Store product `9NN5N0L2TMTF`. Credentials are passed through
302+
uploads the package against Store product `9NN5N0L2TMTF`.
303+
304+
**`--uploadTimeout` is not optional.** Left unset, the CLI passes zero to the blob
305+
client's `Retry.NetworkTimeout`, so every upload attempt is cancelled the instant it
306+
starts and the publish fails with `Retry failed after 6 tries` and a configured timeout of
307+
`0:00:00` — which reads like a network fault and is not one. The Learn documentation does
308+
not mention the option; `msstore publish --help` does. It is per attempt, so
309+
`storeUploadTimeoutSeconds` is set generously rather than tightly. Credentials are passed through
303310
the environment rather than the command line, because the agent echoes a native command
304311
line and log masking is a safety net rather than a guarantee.
305312

@@ -317,9 +324,12 @@ Three things about it are deliberate:
317324
from the last published submission untouched. Changing them is `msstore submission
318325
updateMetadata` and stays a deliberate act in Partner Center, recorded in
319326
`installer/msix/STORE-LISTING.md`.
320-
- **It does not clear a pending submission.** If one is already in flight `msstore publish`
321-
fails, which is correct: that submission may be a person's listing edit, and a pipeline
322-
must not discard it. Resolve it in Partner Center and re-run the stage.
327+
- **It clears an abandoned submission, and only that.** A failed upload leaves a created
328+
but empty submission behind, which would block every later attempt — one failure would
329+
wedge the automation until someone visited Partner Center. Status `PendingCommit` means
330+
exactly that case: created, never committed, nothing in certification. The stage deletes
331+
it and continues. Every other status is left alone and fails the stage, because an
332+
in-flight submission may be a person's listing edit and a pipeline must not discard it.
323333

324334
The first submission was manual, on 20 August 2026 for 0.9.2, because the listing,
325335
screenshots, and age rating are one-time work no API performs — and holding the automation

0 commit comments

Comments
 (0)