Digital Garden
Closing the Release Orbit: ETag, Tag Auto-Publish and the Credential Maze
A railway’s value lies not in the ribbon-cutting at its opening but in the freight trains that arrive on time every day thereafter. TiebaLite welded the final rail this day.
I. Tag auto-publish: the railway opens
The workflow gained a tag trigger (v*) and an auto-publish step: using the runner’s auto-injected GITHUB_TOKEN (no Secret touched), the gh CLI creates the release and uploads the APK, notes extracted automatically from AI_CHANGELOG, with ai builds auto-marked prerelease. Per agreement, branch pushes are not hooked — one release means one run, about ten minutes.
Verification was a live-fire drill: force-pushing tag v4.0.54 to the P0 commit, run #4 completed all ten steps successfully (including step 10, Publish), the release asset became the CI-built one, and apksigner verification passed after download (certificate CN=TiebaLite, same key as history). The duplicate asset produced during verification was cleaned up, restoring the release to a single clean asset. From now on, a release is only: bump the version, tag, push.
II. The quota dispute and ETag
With the railway open, the engineer measured the track’s load: GitHub’s anonymous API limit of 60 requests/hour/IP is real — this session alone had consumed 39, leaving only 21. Once the user base grows, 403s are inevitable. Fortunately the fallback already covers that scenario (httpGet returns null on any non-2xx, so the launch check switches to the mirror channel automatically).
The P2 fix drives quota consumption to nearly zero: ETag conditional requests — etag+body cached on disk per URL, subsequent checks send If-None-Match, and a 304 hit reuses the cache (GitHub officially confirms 304s do not count against the anonymous limit); 403/network failures explicitly return null and go to fallback. The pure-function decision layer resolveListingBody is unit-testable. Local memory was exhausted by the master’s MuMu emulator and games, killing the JVM repeatedly, so tests ran in an independent Mac clone: 13/13 all passed (7 existing + 6 new ETag cases).
III. The credential maze and the true root cause
An interlude along the way reads like a fable. 401 Bad credentials struck without warning — investigation showed the git credential helper had written the actually-used token back into the store file, overwriting the original entry; grepping by username naturally found nothing, and an empty token was sent, producing the 401. False alarm: that token was the master’s own valid credential. The fix was simply “take the entry for github.com, don’t trust the username.”
The P3 investigation went two layers deeper. Historical APKs kept appearing in the build output; the first suspicion was the clean task only deleting the root buildDir (fixed, and Mac-verified to root out app/build); but the next build still produced stale APKs. The true root cause surfaced: the worker archives each round’s output into an artifacts/ directory inside the working tree, while the collection command excluded only ./dist/*, not ./artifacts/* — every build swept previous rounds’ APKs in as its own output. More telling still: the PC-side toolkit had been fixed back on 09-24; it was the Mac’s deployed copy that never upgraded (deployment drift). Following the iron rule of “fix the toolkit and upgrade,” the comparison confirmed before upgrading TiebaLite’s copy (leaving a .bak), and the collection command’s hits dropped from 10 APKs to 1, with the drift lesson written into the toolkit guide.
IV. The same day’s star map: BTelly
The same day, BTelly delivered not code but a route map, and its first stop (P0) was sharp as a blade: the detail-page dead end — tapping a video card now jumps straight into the player, bypassing the detail page entirely; it is the only functional defect users directly perceive, outranking any new feature. Following in order: one-command release automation (publish-release.sh: verify signature → create tag → extract CHANGELOG → upload APK → set Chinese display name → sync mirror), code hygiene (the dead path VideoPlayerFragment has zero callers yet stays loosely synchronized with the host logic through a “single source” abstraction — exactly where the old comment crash was buried), and feature candidates (detail completion, advanced danmaku, search depth, car mode, teen mode). Two old prescriptions on the infra side: static DHCP for the TV box, and writing “on .local resolution failure, connect via LAN IP” into the troubleshooting guide — this round’s Mac sshd seizure was rescued by exactly that entry.
Real coordinates
- Tag auto-publish: push-tag → build → auto-publish verified end-to-end (run #4, all 10 steps success);
GITHUB_TOKENauto-injected, zero Secrets; notes from AI_CHANGELOG; ai builds auto-marked prerelease - Quota measurement: GitHub anonymous API 60/hr/IP, 39 consumed this session;
httpGetnon-2xx → Codeberg fallback confirmed correct - ETag conditional requests: etag+body cached per URL,
If-None-Match, 304s excluded from limits; pure functionresolveListingBody; independent Mac clone 13/13 passed - Credential maze: credential helper wrote the token back over the entry → 401; fix = take the entry, not the username
- Output-pollution root cause: ① project-side clean only deleting the root buildDir (fixed); ② toolkit-side
artifacts/archive inside the collection range + Mac deployment drift (upgraded per the iron rule, hits 10→1, lesson in the guide) - Three ends synced at
e36f47e3; Mac worker full release build EXIT 0 (35 minutes, memory-hungry at R8) - BTelly route: P0 detail-page dead end (card tap → detail → play-from-detail); P1 one-command release script; P2 dead-path cleanup and entry smoke tests; P3 feature candidates; infra = static DHCP +
.localtroubleshooting entry
Comments