[Bug][CI][Deploy] lesson-video-processor has NEVER deployed — the VIDEOS_S3_SECRET_KEY Actions secret was never created, so its own deploy guard rejects every run
#581
QA Team — found while verifying CI on backend PR #280 (2026-07-14).
spikersoft-lesson-video-processordoes not exist in the swarm and never has. The README audit flagged this as an open question ("not yet deployed, deployed under an unmatched name, or gated like Scheduler #540?"). This is the answer, and it is not a mystery — the pipeline has been rejecting its own deploy on every single run.
Root cause
.gitea/workflows/spikersoft-lesson-video-processor.yml guards the deploy on a non-empty secret (lines 112-115):
script:| if [ -z "$VIDEOS_S3_SECRET_KEY" ]; then
echo "VIDEOS_S3_SECRET_KEY is empty — refusing to deploy (check the repo Actions secret)."
exit 1
fi
cd /mnt/infrastructure/spikersoft-lesson-video-processor
sudo VIDEOS_S3_SECRET_KEY="$VIDEOS_S3_SECRET_KEY" \
docker stack deploy --with-registry-auth -c docker-stack.yml spikersoft-lesson-video-processor
The secret is empty. Straight from the runner log (job 24483, run #5655, 2026-07-14 19:45:07Z):
expression '${{ secrets.VIDEOS_S3_SECRET_KEY }}' rewritten to 'format('{0}', secrets.VIDEOS_S3_SECRET_KEY)'
evaluating expression 'format('{0}', secrets.VIDEOS_S3_SECRET_KEY)'
expression 'format('{0}', secrets.VIDEOS_S3_SECRET_KEY)' evaluated to '%!t(string=)'
...
❌ Failure - Main Run entrypoint.sh
exitcode '1': failure
❌ Failure - Main Deploy to production
%!t(string=) is Go formatting an empty string — the repo Actions secret VIDEOS_S3_SECRET_KEY does not exist. The guard does exactly what it was written to do, and has presumably been doing it since the guard was added.
What this means
The images are fine. Both build_and_publish legs (amd64 + arm64v8) succeed, and docker manifest create / docker manifest push succeed — the log shows Created manifest list git.spikersoft.com/spikerj/spikersoft-lesson-video-processor:latest. Only the deploy step fails.
So the service has simply never been created.docker service ls | grep lesson returns nothing.
create_manifest shows red on every PR that touches this project, which is exactly the kind of permanently-red leg that trains people to ignore CI. It went red on a docs-only PR.
This is the same failure shape as #540 (Scheduler was never wired into CI and nobody noticed it wasn't running). Same lesson: a service that never deploys produces no errors, because it produces nothing at all.
Fix
Create the VIDEOS_S3_SECRET_KEY repo Actions secret — the scoped MinIO key for the lesson-videos dual-write (per the workflow comment: #523, cutover batch #529).
Re-run the workflow and confirm spikersoft-lesson-video-processor actually appears in docker service ls.
Then settle the open question this blocks: who serves /lesson-videos in production today — nginx static hosting, the API's /lesson-videos static route, or both? LessonVideoMirror.cs claims both, and that cannot be verified while the worker has never run (#528/#531 cutover).
Related
The create_manifest deploy-gate pattern is shared across the backend multi-arch workflows, so it is worth checking whether any other service is silently failing the same guard on a missing secret. scheduler (#540) and this one make two.
Found during backend PR #280 (README audit); the audit's open question about this service's deployment status can now be answered from this ticket.
**QA Team** — found while verifying CI on backend PR #280 (2026-07-14).
`spikersoft-lesson-video-processor` **does not exist in the swarm** and never has. The README audit flagged this as an open question ("not yet deployed, deployed under an unmatched name, or gated like Scheduler #540?"). This is the answer, and it is not a mystery — the pipeline has been rejecting its own deploy on every single run.
## Root cause
`.gitea/workflows/spikersoft-lesson-video-processor.yml` guards the deploy on a non-empty secret (lines 112-115):
```yaml
script: |
if [ -z "$VIDEOS_S3_SECRET_KEY" ]; then
echo "VIDEOS_S3_SECRET_KEY is empty — refusing to deploy (check the repo Actions secret)."
exit 1
fi
cd /mnt/infrastructure/spikersoft-lesson-video-processor
sudo VIDEOS_S3_SECRET_KEY="$VIDEOS_S3_SECRET_KEY" \
docker stack deploy --with-registry-auth -c docker-stack.yml spikersoft-lesson-video-processor
```
The secret is **empty**. Straight from the runner log (job 24483, run #5655, 2026-07-14 19:45:07Z):
```
expression '${{ secrets.VIDEOS_S3_SECRET_KEY }}' rewritten to 'format('{0}', secrets.VIDEOS_S3_SECRET_KEY)'
evaluating expression 'format('{0}', secrets.VIDEOS_S3_SECRET_KEY)'
expression 'format('{0}', secrets.VIDEOS_S3_SECRET_KEY)' evaluated to '%!t(string=)'
...
❌ Failure - Main Run entrypoint.sh
exitcode '1': failure
❌ Failure - Main Deploy to production
```
`%!t(string=)` is Go formatting an **empty string** — the repo Actions secret `VIDEOS_S3_SECRET_KEY` does not exist. The guard does exactly what it was written to do, and has presumably been doing it since the guard was added.
## What this means
- **The images are fine.** Both `build_and_publish` legs (amd64 + arm64v8) succeed, and `docker manifest create` / `docker manifest push` succeed — the log shows `Created manifest list git.spikersoft.com/spikerj/spikersoft-lesson-video-processor:latest`. Only the deploy step fails.
- **So the service has simply never been created.** `docker service ls | grep lesson` returns nothing.
- **`create_manifest` shows red on every PR that touches this project**, which is exactly the kind of permanently-red leg that trains people to ignore CI. It went red on a **docs-only** PR.
This is the same failure shape as **#540** (Scheduler was never wired into CI and nobody noticed it wasn't running). Same lesson: a service that never deploys produces no errors, because it produces nothing at all.
## Fix
1. Create the `VIDEOS_S3_SECRET_KEY` repo Actions secret — the scoped MinIO key for the lesson-videos dual-write (per the workflow comment: #523, cutover batch #529).
2. Re-run the workflow and confirm `spikersoft-lesson-video-processor` actually appears in `docker service ls`.
3. Then settle the **open question this blocks**: who serves `/lesson-videos` in production today — nginx static hosting, the API's `/lesson-videos` static route, or both? `LessonVideoMirror.cs` claims both, and that cannot be verified while the worker has never run (#528/#531 cutover).
## Related
- The `create_manifest` deploy-gate pattern is shared across the backend multi-arch workflows, so it is worth checking whether any **other** service is silently failing the same guard on a missing secret. `scheduler` (#540) and this one make two.
- Found during backend PR #280 (README audit); the audit's open question about this service's deployment status can now be answered from this ticket.
Resolved in spikersoft-backend PR #303 (batch 3a, merged to master — commit 13d6ffb3). spikersoft-lesson-video-processor.yml now fetches VIDEOS_S3_SECRET_KEY from OpenBao (secret/ci/backend/minio/videos, a verified-populated path) via the bao-secrets action, instead of the VIDEOS_S3_SECRET_KEY Actions secret that was never created. The deploy guard now sees a real key, so the pipeline can deploy for the first time. Closing.
Resolved in spikersoft-backend PR #303 (batch 3a, merged to `master` — commit `13d6ffb3`). `spikersoft-lesson-video-processor.yml` now fetches `VIDEOS_S3_SECRET_KEY` from OpenBao (`secret/ci/backend/minio/videos`, a verified-populated path) via the `bao-secrets` action, instead of the `VIDEOS_S3_SECRET_KEY` Actions secret that was never created. The deploy guard now sees a real key, so the pipeline can deploy for the first time. Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
QA Team — found while verifying CI on backend PR #280 (2026-07-14).
spikersoft-lesson-video-processordoes not exist in the swarm and never has. The README audit flagged this as an open question ("not yet deployed, deployed under an unmatched name, or gated like Scheduler #540?"). This is the answer, and it is not a mystery — the pipeline has been rejecting its own deploy on every single run.Root cause
.gitea/workflows/spikersoft-lesson-video-processor.ymlguards the deploy on a non-empty secret (lines 112-115):The secret is empty. Straight from the runner log (job 24483, run #5655, 2026-07-14 19:45:07Z):
%!t(string=)is Go formatting an empty string — the repo Actions secretVIDEOS_S3_SECRET_KEYdoes not exist. The guard does exactly what it was written to do, and has presumably been doing it since the guard was added.What this means
build_and_publishlegs (amd64 + arm64v8) succeed, anddocker manifest create/docker manifest pushsucceed — the log showsCreated manifest list git.spikersoft.com/spikerj/spikersoft-lesson-video-processor:latest. Only the deploy step fails.docker service ls | grep lessonreturns nothing.create_manifestshows red on every PR that touches this project, which is exactly the kind of permanently-red leg that trains people to ignore CI. It went red on a docs-only PR.This is the same failure shape as #540 (Scheduler was never wired into CI and nobody noticed it wasn't running). Same lesson: a service that never deploys produces no errors, because it produces nothing at all.
Fix
VIDEOS_S3_SECRET_KEYrepo Actions secret — the scoped MinIO key for the lesson-videos dual-write (per the workflow comment: #523, cutover batch #529).spikersoft-lesson-video-processoractually appears indocker service ls./lesson-videosin production today — nginx static hosting, the API's/lesson-videosstatic route, or both?LessonVideoMirror.csclaims both, and that cannot be verified while the worker has never run (#528/#531 cutover).Related
create_manifestdeploy-gate pattern is shared across the backend multi-arch workflows, so it is worth checking whether any other service is silently failing the same guard on a missing secret.scheduler(#540) and this one make two.Resolved in spikersoft-backend PR #303 (batch 3a, merged to
master— commit13d6ffb3).spikersoft-lesson-video-processor.ymlnow fetchesVIDEOS_S3_SECRET_KEYfrom OpenBao (secret/ci/backend/minio/videos, a verified-populated path) via thebao-secretsaction, instead of theVIDEOS_S3_SECRET_KEYActions secret that was never created. The deploy guard now sees a real key, so the pipeline can deploy for the first time. Closing.