Skip to content

Commit 87bad36

Browse files
authored
fix(update): startup readiness and reliability cleanups (#30)
* docs(agents): add simplicity scope discipline * fix(background): preserve terminal recovery state * refactor(memory): remove unused retriever seam * fix(shell): echo resolved prompt text * docs(agents): discourage over-fragmentation * fix(update): revalidate stale startup cache * docs: document reliability fixes * test: update reliability regressions
1 parent e8cb860 commit 87bad36

27 files changed

Lines changed: 1101 additions & 196 deletions

.github/workflows/promote-release.yml

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ name: Promote release
33
# Platform build workflows (windows-installer, linux-installer,
44
# release-pythinker-cli) create the GitHub Release as a PRERELEASE so it stays
55
# out of the date-based /releases/latest endpoint (which ignores make_latest)
6-
# until every asset has uploaded. This workflow waits for all expected assets,
7-
# then clears `prerelease` and marks the release latest — the single point
8-
# where a version becomes resolvable by the install scripts and in-app updater.
6+
# until every install channel is ready. This workflow waits for exact release
7+
# assets, PyPI, and the Homebrew formula, then clears `prerelease` and marks the
8+
# release latest — the single point where a version becomes resolvable by the
9+
# install scripts and in-app updater.
910
#
1011
# It runs on the tag push (not `release: published`, which a GITHUB_TOKEN-created
1112
# release never fires) so promotion always happens. workflow_dispatch allows a
@@ -55,24 +56,44 @@ jobs:
5556
fi
5657
echo "tag=$tag" >> "$GITHUB_OUTPUT"
5758
58-
- name: Wait for all release assets
59+
- name: Wait for install-channel readiness
5960
env:
6061
GH_TOKEN: ${{ github.token }}
6162
TAG: ${{ steps.tag.outputs.tag }}
6263
REPO: ${{ github.repository }}
6364
run: |
6465
set -euo pipefail
65-
required=(
66-
"PythinkerSetup-"
67-
"_amd64.deb"
68-
"_arm64.deb"
69-
".x86_64.rpm"
70-
".aarch64.rpm"
71-
"x86_64-unknown-linux-gnu.tar.gz"
72-
"aarch64-unknown-linux-gnu.tar.gz"
73-
"aarch64-apple-darwin.tar.gz"
74-
"x86_64-apple-darwin.tar.gz"
66+
version="${TAG#v}"
67+
required_assets=(
68+
"PythinkerSetup-${version}.exe"
69+
"PythinkerSetup-${version}.exe.sha256"
70+
"pythinker-code_${version}_amd64.deb"
71+
"pythinker-code_${version}_amd64.deb.sha256"
72+
"pythinker-code_${version}_arm64.deb"
73+
"pythinker-code_${version}_arm64.deb.sha256"
74+
"pythinker-code-${version}.x86_64.rpm"
75+
"pythinker-code-${version}.x86_64.rpm.sha256"
76+
"pythinker-code-${version}.aarch64.rpm"
77+
"pythinker-code-${version}.aarch64.rpm.sha256"
78+
"pythinker-${version}-x86_64-unknown-linux-gnu.tar.gz"
79+
"pythinker-${version}-x86_64-unknown-linux-gnu.tar.gz.sha256"
80+
"pythinker-${version}-aarch64-unknown-linux-gnu.tar.gz"
81+
"pythinker-${version}-aarch64-unknown-linux-gnu.tar.gz.sha256"
82+
"pythinker-${version}-aarch64-apple-darwin.tar.gz"
83+
"pythinker-${version}-aarch64-apple-darwin.tar.gz.sha256"
84+
"pythinker-${version}-x86_64-apple-darwin.tar.gz"
85+
"pythinker-${version}-x86_64-apple-darwin.tar.gz.sha256"
86+
"pythinker-${version}-x86_64-unknown-linux-gnu-onedir.tar.gz"
87+
"pythinker-${version}-x86_64-unknown-linux-gnu-onedir.tar.gz.sha256"
88+
"pythinker-${version}-aarch64-unknown-linux-gnu-onedir.tar.gz"
89+
"pythinker-${version}-aarch64-unknown-linux-gnu-onedir.tar.gz.sha256"
90+
"pythinker-${version}-aarch64-apple-darwin-onedir.tar.gz"
91+
"pythinker-${version}-aarch64-apple-darwin-onedir.tar.gz.sha256"
92+
"pythinker-${version}-x86_64-apple-darwin-onedir.tar.gz"
93+
"pythinker-${version}-x86_64-apple-darwin-onedir.tar.gz.sha256"
7594
)
95+
pypi_url="https://pypi.org/pypi/pythinker-code/${version}/json"
96+
homebrew_formula_url="https://raw.githubusercontent.com/TechMatrix-labs/homebrew-pythinker/main/Formula/pythinker-code.rb"
7697
# The budget must comfortably exceed the slowest platform build, since
7798
# this job runs on the tag push in parallel with them. The long pole is
7899
# linux-installer's emulated arm64 .deb/.rpm step: on the 0.26.0 release
@@ -83,26 +104,51 @@ jobs:
83104
max_attempts=80
84105
poll_interval=30
85106
budget_min=$(( max_attempts * poll_interval / 60 ))
86-
echo "Polling for all release assets on $TAG (up to ${budget_min}m)..."
87-
all_present=false
107+
echo "Polling install-channel readiness for $TAG (up to ${budget_min}m)..."
108+
all_ready=false
88109
for i in $(seq 1 "$max_attempts"); do
89-
assets=$(gh api "repos/$REPO/releases/tags/$TAG" --jq '[.assets[].name] | join(" ")' 2>/dev/null || echo "")
90-
all_present=true
91-
for p in "${required[@]}"; do
92-
if [[ "$assets" != *"$p"* ]]; then
93-
all_present=false
94-
break
110+
assets_json=$(gh api "repos/$REPO/releases/tags/$TAG" --jq '[.assets[].name]' 2>/dev/null || printf '[]')
111+
missing_assets=()
112+
for asset in "${required_assets[@]}"; do
113+
if ! jq -e --arg name "$asset" 'index($name)' <<<"$assets_json" >/dev/null; then
114+
missing_assets+=("$asset")
95115
fi
96116
done
97-
if [[ "$all_present" == "true" ]]; then
98-
echo "All assets present (attempt $i)"
117+
118+
pypi_ready=false
119+
if curl -fsSL --retry 2 --retry-delay 2 -o /dev/null "$pypi_url"; then
120+
pypi_ready=true
121+
fi
122+
123+
homebrew_ready=false
124+
formula_text=$(curl -fsSL --retry 2 --retry-delay 2 "$homebrew_formula_url" 2>/dev/null || true)
125+
if grep -qF "version \"${version}\"" <<<"$formula_text"; then
126+
homebrew_ready=true
127+
fi
128+
129+
if [[ "${#missing_assets[@]}" -eq 0 && "$pypi_ready" == "true" && "$homebrew_ready" == "true" ]]; then
130+
all_ready=true
131+
echo "All install channels ready (attempt $i)"
99132
break
100133
fi
101-
echo "Attempt $i/$max_attempts: assets not ready, retrying in ${poll_interval}s..."
102-
sleep "$poll_interval"
134+
135+
echo "Attempt $i/$max_attempts: install channels not ready."
136+
if [[ "${#missing_assets[@]}" -gt 0 ]]; then
137+
printf 'Missing release assets: %s\n' "${missing_assets[*]}"
138+
fi
139+
if [[ "$pypi_ready" != "true" ]]; then
140+
echo "PyPI is not serving ${version} yet: $pypi_url"
141+
fi
142+
if [[ "$homebrew_ready" != "true" ]]; then
143+
echo "Homebrew tap formula is not at ${version} yet: $homebrew_formula_url"
144+
fi
145+
if [[ "$i" -lt "$max_attempts" ]]; then
146+
echo "Retrying in ${poll_interval}s..."
147+
sleep "$poll_interval"
148+
fi
103149
done
104-
if [[ "$all_present" != "true" ]]; then
105-
echo "::error::Release assets not fully uploaded after ${budget_min} minutes"
150+
if [[ "$all_ready" != "true" ]]; then
151+
echo "::error::Install channels were not fully ready after ${budget_min} minutes"
106152
exit 1
107153
fi
108154

0 commit comments

Comments
 (0)