Lesson 272: Ren'Py SteamPipe Upload Wall-Clock Receipt on BUILD_RECEIPT (2026)
Direct answer: Before November 2026 Ren'Py teams treat a long SteamCMD run as auth failure, promote steampipe_wall_clock_receipt_v1.json with L1–L6 gates—pin the Ren'Py distribution to build_label, capture UTC start/end + exit code, retain a redacted SteamCMD excerpt, wire upload_duration_min on BUILD_RECEIPT, and fail-close wall_clock_log_ok (not setlive_allowed). Pair Ren'Py wall-clock preflight (Guide #34), October wall-clock trend, and cousin Lesson 262 (partner identity—not duration).

Why this matters now (November Ren'Py close-out uploads)
November 2026 visual-novel teams pass Lesson 262 partner checks on a cousin engine, upload from Ren'Py Build Distributions, and watch Steamworks stay Queued for forty minutes. Discord declares the login dead at minute twelve—while steamcmd already exited 0. Upload duration ≠ auth failure ≠ validation wait ≠ branch promotion—four clocks, one receipt lane.
Lesson 271 wires diligence hero mirror; 272 wires Ren'Py SteamPipe wall-clock so Thursday row review diffs upload_duration_min beside smoke—not founder memory of how long Queued lasted.
Beginner path (label → timestamps → excerpt → receipt)
| Step | Action | Success check |
|---|---|---|
| 1 | Match Ren'Py zip to build_label |
L1 distribution label |
| 2 | Record UTC before/after SteamCMD | L2–L3 timestamps + exit code |
| 3 | Save redacted excerpt | L4 auth vs success class |
| 4 | Append validation poll jsonl | Processing → Valid separate |
| 5 | Attach duration to BUILD_RECEIPT | L5 upload_duration_min |
| 6 | File receipt + BUILD_RECEIPT column | wall_clock_log_ok: true |
Time: ~50 minutes first wall-clock wiring; ~5 minutes when Guide #34 preflight template exists.
Developer path (gates L1–L6)
| Gate | Check | Fail when |
|---|---|---|
| L1 | Ren'Py package + build_label |
Zip name drift vs BUILD_RECEIPT |
| L2 | UTC start before SteamCMD | Missing RFC 3339 start |
| L3 | End, duration, exit code | Negative duration; exit omitted |
| L4 | Redacted diagnostic excerpt | Password in evidence |
| L5 | BUILD_RECEIPT duration row | Verbal “upload took forever” |
| L6 | Receipt + BUILD_RECEIPT | Promote before L4 classification |
Align with Guide #34—guide = five-minute gate; 272 = BUILD_RECEIPT milestone with jq fail-closed promotion.
Cousin receipt crosswalk
| Field | Cousin (partner Lesson 262) | Cousin (queue trend blog) | This lesson (272) |
|---|---|---|---|
| Schema | gm_steam_partner_receipt_v1 |
steampipe_queue_wall_clock_receipt_v1 |
steampipe_wall_clock_receipt_v1 |
| Scope | Partner identity | Queue policy narrative | Ren'Py upload log BUILD_RECEIPT |
| Path | release-evidence/steam/ |
release-evidence/steam/ |
release-evidence/steam/renpy-upload/ |
Do not merge schemas—reference paths in cousin_receipts only.
L1 — Ren'Py distribution label
jq -e '.build_label == "nextfest-nov-2026-rc2"' release-evidence/BUILD_RECEIPT.json
test -f dist/my-visual-novel-nextfest-nov-2026-rc2.zip
Producer rule: Ren'Py Build Distributions output name must include the same build_label string as BUILD_RECEIPT and visible build label in-game.
L2–L3 — Wall-clock around SteamCMD
PowerShell release runner:
$evidence = "release-evidence/steam/renpy-upload"
New-Item -ItemType Directory -Force $evidence | Out-Null
$started = (Get-Date).ToUniversalTime()
& steamcmd +login $env:STEAM_PARTNER_USER $env:STEAM_PARTNER_PASS `
+run_app_build "C:\build\steam\app_build_renpy.vdf" +quit `
*> "$evidence\steamcmd-upload.log"
$exitCode = $LASTEXITCODE
$finished = (Get-Date).ToUniversalTime()
$durationSeconds = [math]::Round(($finished - $started).TotalSeconds)
Log started_at_utc, finished_at_utc, duration_seconds, exit_code—never passwords.
L4 — Redacted excerpt + classification
| Signal | Classification | Next action |
|---|---|---|
| Login error + non-zero exit | auth_failure |
Lesson 262 lane |
| VDF/depot error + non-zero | configuration_failure |
Fix mapping; do not retry unchanged |
| Success marker + exit 0 | upload_command_success |
Poll Steamworks separately |
| Exit 0, no marker | ambiguous_success |
Inspect full restricted log |
Queued in Steamworks after exit 0 is not auth failure—append validation poll rows.
L5 — BUILD_RECEIPT upload_duration_min
{
"build_label": "nextfest-nov-2026-rc2",
"steampipe_upload": {
"duration_min": 18.4,
"exit_code": 0,
"classification": "upload_command_success",
"receipt": "release-evidence/steam/renpy-upload/STEAMPIPE_WALL_CLOCK_RECEIPT.json"
}
}
Pair October wall-clock trend Q1–Q6 queue policy—272 owns Ren'Py BUILD_RECEIPT column.
L6 — steampipe_wall_clock_receipt_v1.json
{
"schema": "steampipe_wall_clock_receipt_v1",
"engine": "renpy",
"build_label": "nextfest-nov-2026-rc2",
"distribution_path": "dist/my-visual-novel-nextfest-nov-2026-rc2.zip",
"app_build_vdf": "build/steam/app_build_renpy.vdf",
"started_at_utc": "2026-11-12T18:04:11.0000000Z",
"finished_at_utc": "2026-11-12T18:22:36.0000000Z",
"duration_seconds": 1105,
"exit_code": 0,
"classification": "upload_command_success",
"log_excerpt": "release-evidence/steam/renpy-upload/steamcmd-upload-excerpt.log",
"validation_poll_log": "release-evidence/steam/renpy-upload/validation-poll.jsonl",
"cousin_receipts": {
"partner_identity": "release-evidence/steam/GM_STEAM_PARTNER_RECEIPT.json",
"queue_wall_clock": "release-evidence/steam/STEAMPIPE_QUEUE_WALL_CLOCK_RECEIPT.json",
"build": "release-evidence/BUILD_RECEIPT.json"
},
"gates": {
"L1_distribution_label": "pass",
"L2_started_at_utc": "pass",
"L3_end_duration_exit": "pass",
"L4_redacted_excerpt": "pass",
"L5_build_receipt_row": "pass",
"L6_receipt": "pass"
},
"wall_clock_log_ok": true,
"setlive_allowed": false
}
Pin at release-evidence/steam/renpy-upload/STEAMPIPE_WALL_CLOCK_RECEIPT.json.
Fail-closed jq (CI tail)
REC=release-evidence/steam/renpy-upload/STEAMPIPE_WALL_CLOCK_RECEIPT.json
jq -e '
.schema == "steampipe_wall_clock_receipt_v1" and
.engine == "renpy" and
(.duration_seconds | type == "number" and . >= 0) and
(.gates | to_entries | all(.value == "pass")) and
.wall_clock_log_ok == true and
.setlive_allowed == false
' "$REC"
Exit 0 proves log evidence complete—it does not authorize setlive.
Mini exercise (45 minutes)
- Run staging upload; capture L2–L4.
- Compute duration from UTC timestamps—not console approximations.
- Append one Processing and one Valid poll row.
- Wire L5 on BUILD_RECEIPT.
- Validate L6 jq; confirm
setlive_allowedstays false.
Continuity — November 2026 fest close-out + playtest triage (266–276)
| Lesson | Receipt focus |
|---|---|
| 271 | Blender diligence mirror |
| 272 (this) | Ren'Py SteamPipe wall-clock |
| 273 | Audacity loudness spot (forward) |
Previous: Lesson 271 — Blender diligence hero GLB rclone mirror receipt
Next: Lesson 273 — Audacity playtest VOD loudness spot Whisper receipt
FAQ
Same as Guide #34?
Guide = five-minute L1–L6 preflight; 272 owns steampipe_wall_clock_receipt_v1 BUILD_RECEIPT column + jq promotion.
Same as October wall-clock blog?
Blog owns queue policy + steampipe_queue_wall_clock_receipt_v1 narrative; 272 owns Ren'Py engine row on BUILD_RECEIPT.
Same as Lesson 262?
262 = partner identity P1–P6; 272 = upload duration L1–L6 after partner GREEN.
Does exit 0 mean setlive?
No—receipt keeps setlive_allowed: false; branch promotion needs Valid build + Lesson 270 cousin checks for GameMaker SKUs.
Re-upload when Queued?
No—append validation poll; see 14 free SteamPipe observability tools.
Related GamineAI reads
- Ren'Py wall-clock preflight (Guide #34)
- October SteamPipe queue wall-clock trend
- 14 free SteamPipe observability tools
- Thursday BUILD_RECEIPT review
Queued is not failed—capture UTC wall-clock, classify the excerpt, file steampipe_wall_clock_receipt_v1, then poll validation before any branch promotion.