Overview
RunReport.write() has emitted a consolidated smoke_timings.json in every
report dir since PyAutoHands d2a22f4, and PyAutoHeart#167 published it from
the PR gate (smoke-tests.yml) as smoke-timings-<python-version>. The
weekly sweep goes through a different body — workspace-validation.yml —
which got no such upload, so the widest timing sample the organism produces
persists only inside the per-leg results-* zips, under no name a consumer
can glob for, and expires with those zips at 30 days.
Verified on the 2026-08-24 weekly run 32688412626 (success, 136 jobs): all
109 artifacts are results-* / notebooks-* / workspace-validation-report
— not one smoke-timings-*. The legs do emit the file (the
results-scripts-autolens-imaging artifact grew 2 files / 3,950 B → 3 files /
5,306 B between the 2026-08-19 and 2026-08-24 runs); only the naming is
missing.
This takes option (a) from the prompt — mirror the PR gate's named upload
into workspace-validation.yml — rather than (b), deferring the whole contract
to a phase-3 ingester that does not exist yet. (a) makes the weekly data
discoverable now and leaves the future ingester with one glob
(smoke-timings-*) instead of two.
Plan
- Add a named
smoke-timings-* upload to workspace-validation.yml's
run_scripts and run_notebooks legs, alongside (not replacing) the
existing results-* uploads.
- Carry the leg in the artifact name —
smoke-timings-<leg>-<project>-<directory>
— because the weekly run has ~50 legs where the PR gate has two, so the PR
gate's fixed name would collide here. That is the same (project, directory)
pair the sibling results-* names already rely on to stay unique.
- Upload only
smoke_timings.json, not the whole report dir: the report dir
already ships as results-*, and duplicating ~50 of them weekly buys nothing.
- Give the timings no
retention-days, so they keep the repo's full default
window (the results-* zips they came from expire at 30 days) — the same
reasoning the PR gate's upload records.
- Keep the new artifacts out of the
results-* namespace, which the analyze
job downloads and feeds to aggregate_results.py — that consumer skips the
timing sidecar by name.
- Pin all of the above in
tests/test_workflow_wiring.py, next to the existing
PR-gate timing tests.
- Leave
smoke-tests.yml untouched: no change to PR-gate behaviour.
Detailed implementation plan
Work Classification
Library (Heart's own CI wiring) — no workspace repo is touched.
Affected Repositories
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./PyAutoHeart |
main |
clean |
Other branches on the remote: main, claude/health-agent-implementation-x3z8w6,
archive/condemned/pyautoheart-feature-autonerves-verify-install. No conflict —
nothing else claims PyAutoHeart.
Suggested branch: claude/weekly-smoke-timings-naming-012fuj
Worktree root: ~/Code/PyAutoLabs-wt/weekly-smoke-timings-naming/ (local-dev
only; this run is a web-github session working from the session clone).
Implementation Steps
-
.github/workflows/workspace-validation.yml, job run_scripts — after the
existing Upload script results step, add:
- name: Upload the per-script timings (scripts leg) under a globbable name
if: always()
uses: actions/upload-artifact@v4
with:
name: smoke-timings-scripts-${{ matrix.project.name }}-${{ matrix.project.directory }}
path: workspace/**/smoke_timings.json
if-no-files-found: ignore
with a comment block recording why it is a separate artifact (the name is
the ingester's contract, and results-* is the aggregate consumer's), why
the name carries the leg (~50 legs vs the gate's two), why only the JSON,
and why there is no retention-days.
-
Same job run_notebooks, after Upload notebook results — the mirror step,
named smoke-timings-notebooks-…, keeping that leg's existing gate
always() && steps.gate.outputs.run == 'true' (the *_test workspaces
publish no notebooks, so those legs never execute).
-
tests/test_workflow_wiring.py — six tests appended beside the existing
PR-gate timing tests:
test_validation_body_publishes_the_timing_dataset — both legs upload
smoke_timings.json under a smoke-timings-* name.
test_validation_timing_artifact_names_carry_the_leg — the exact
collision-free name, per leg.
test_validation_timing_upload_cannot_fail_the_weekly_sweep — always()
if-no-files-found: ignore.
test_validation_notebook_timing_upload_keeps_the_no_notebooks_gate.
test_validation_timing_dataset_keeps_full_default_retention — no
retention-days key.
test_timing_artifacts_stay_out_of_the_aggregate_namespace — analyze
still downloads pattern: results-*, and neither new name starts with
results-.
Key Files
.github/workflows/workspace-validation.yml — the weekly sweep's body; the
two new upload steps.
.github/workflows/smoke-tests.yml — the PR gate. Untouched; its
smoke-timings-<python-version> upload is the pattern being mirrored.
tests/test_workflow_wiring.py — the wiring pins.
- PyAutoHands
autohands/result_collector.py — RunReport.write(), the
producer of smoke_timings.json. Not modified.
Trade-offs
- (a) vs (b). (b) would defer the whole thing to the phase-3 Heart-board
ingester by widening its contract to glob results-* too. That contract
does not exist yet (nothing in PyAutoHeart references smoke-timings outside
smoke-tests.yml and its tests), so (b) means writing a promise instead of a
fix, and leaves the timings expiring at the results-* 30-day retention. (a)
is also symmetric with the PR gate, which is the shape a future ingester
wants.
- Separate artifact vs widening the
results-* payload. Keeping them apart
costs one extra upload step per leg but keeps the two consumer contracts
clean — aggregate_results.py globs **/*.json under results-* and
excludes the sidecar by name; feeding it a second copy is the wrong
direction.
mode=release also gets the upload. run_scripts runs in both modes and
the step is unconditional, so a release-integration run publishes its timings
too. Harmless and arguably useful; the project set is a subset there, so no
new collision risk.
Testing
python3 -m pytest tests/ — full Heart suite, on the branch.
- The only true end-to-end proof is a weekly
workspace-smoke.yml run (or a
manual workflow_dispatch of it): the artifact list should gain
smoke-timings-scripts-* / smoke-timings-notebooks-* entries with no
duplicate-name failures across the ~50 legs.
Original Prompt
Click to expand starting prompt
The weekly smoke run's timings land in results-* under no discoverable name
Type: maintenance
Target: pyautoheart
Repos:
- @PyAutoHeart
Difficulty: small
Autonomy: safe
Priority: low
Status: draft
Filed: 2026-08-24
Follow-on gap from complete/2026/08/smoke-timings-dataset.md (PyAutoHands
d2a22f4 + PyAutoHeart#167 3df42b5, merged 2026-08-24). All facts below
verified 2026-08-24 via the Actions API.
The gap
RunReport.write() now emits smoke_timings.json (schema smoke_timings/1)
in every report dir, but the named upload
(smoke-timings-<python-version>, if: always(), if-no-files-found: ignore)
was added only to the reusable PR-gate workflow
@PyAutoHeart/.github/workflows/smoke-tests.yml.
The weekly workspace-smoke.yml run goes through
@PyAutoHeart/.github/workflows/workspace-validation.yml, which has no such
named upload — e.g. run 32688412626 (2026-08-24 04:00 UTC, success,
136 jobs).
Its run_scripts / generate_notebooks legs do emit the file now. Verified
differentially: the results-scripts-autolens-imaging artifact grew from
2 files / 3,950 bytes (2026-08-19, run 32277952488) to 3 files /
5,306 bytes (2026-08-24, PyAutoHands at d2a22f4). So the weekly timing
data — by far the widest sample the organism produces — persists only inside
the per-leg results-* zips, under no name a consumer can glob for.
Task
Pick one and record the decision in the PR body:
- (a) Add the same named upload to
workspace-validation.yml's script and
notebook legs (if: always(), if-no-files-found: ignore). Simpler and
symmetric with the PR gate — preferred unless (b) proves cheaper.
- (b) Explicitly commit the deferred phase-3 Heart-board timing
ingester (the follow-up named in complete/2026/08/smoke-timings-dataset.md)
to globbing smoke_timings.json out of results-* artifacts as well as
smoke-timings-* ones, and say so in that contract.
Trap for (a): artifact-name collisions. The PR gate has one leg per python
version, so a single fixed name is safe there. The weekly run has ~50+ legs in
one run, so a fixed smoke-timings-<python-version> would collide — the name
must carry the leg's package/directory (mirroring how results-* is already
disambiguated, e.g. results-scripts-autolens-imaging).
Acceptance
- Weekly-run timing data is either published under a predictable
smoke-timings-* name, or the ingester contract explicitly covers
results-* — not left implicit either way.
- No change to PR-gate behaviour (
smoke-tests.yml untouched, or provably
equivalent).
workspace-validation.yml stays green; no artifact-name collisions in a
full weekly run.
Overview
RunReport.write()has emitted a consolidatedsmoke_timings.jsonin everyreport dir since PyAutoHands
d2a22f4, and PyAutoHeart#167 published it fromthe PR gate (
smoke-tests.yml) assmoke-timings-<python-version>. Theweekly sweep goes through a different body —
workspace-validation.yml—which got no such upload, so the widest timing sample the organism produces
persists only inside the per-leg
results-*zips, under no name a consumercan glob for, and expires with those zips at 30 days.
Verified on the 2026-08-24 weekly run
32688412626(success, 136 jobs): all109 artifacts are
results-*/notebooks-*/workspace-validation-report— not one
smoke-timings-*. The legs do emit the file (theresults-scripts-autolens-imagingartifact grew 2 files / 3,950 B → 3 files /5,306 B between the 2026-08-19 and 2026-08-24 runs); only the naming is
missing.
This takes option (a) from the prompt — mirror the PR gate's named upload
into
workspace-validation.yml— rather than (b), deferring the whole contractto a phase-3 ingester that does not exist yet. (a) makes the weekly data
discoverable now and leaves the future ingester with one glob
(
smoke-timings-*) instead of two.Plan
smoke-timings-*upload toworkspace-validation.yml'srun_scriptsandrun_notebookslegs, alongside (not replacing) theexisting
results-*uploads.smoke-timings-<leg>-<project>-<directory>— because the weekly run has ~50 legs where the PR gate has two, so the PR
gate's fixed name would collide here. That is the same
(project, directory)pair the sibling
results-*names already rely on to stay unique.smoke_timings.json, not the whole report dir: the report diralready ships as
results-*, and duplicating ~50 of them weekly buys nothing.retention-days, so they keep the repo's full defaultwindow (the
results-*zips they came from expire at 30 days) — the samereasoning the PR gate's upload records.
results-*namespace, which theanalyzejob downloads and feeds to
aggregate_results.py— that consumer skips thetiming sidecar by name.
tests/test_workflow_wiring.py, next to the existingPR-gate timing tests.
smoke-tests.ymluntouched: no change to PR-gate behaviour.Detailed implementation plan
Work Classification
Library (Heart's own CI wiring) — no workspace repo is touched.
Affected Repositories
Branch Survey
Other branches on the remote:
main,claude/health-agent-implementation-x3z8w6,archive/condemned/pyautoheart-feature-autonerves-verify-install. No conflict —nothing else claims PyAutoHeart.
Suggested branch:
claude/weekly-smoke-timings-naming-012fujWorktree root:
~/Code/PyAutoLabs-wt/weekly-smoke-timings-naming/(local-devonly; this run is a
web-githubsession working from the session clone).Implementation Steps
.github/workflows/workspace-validation.yml, jobrun_scripts— after theexisting
Upload script resultsstep, add:with a comment block recording why it is a separate artifact (the name is
the ingester's contract, and
results-*is the aggregate consumer's), whythe name carries the leg (~50 legs vs the gate's two), why only the JSON,
and why there is no
retention-days.Same job
run_notebooks, afterUpload notebook results— the mirror step,named
smoke-timings-notebooks-…, keeping that leg's existing gatealways() && steps.gate.outputs.run == 'true'(the*_testworkspacespublish no notebooks, so those legs never execute).
tests/test_workflow_wiring.py— six tests appended beside the existingPR-gate timing tests:
test_validation_body_publishes_the_timing_dataset— both legs uploadsmoke_timings.jsonunder asmoke-timings-*name.test_validation_timing_artifact_names_carry_the_leg— the exactcollision-free name, per leg.
test_validation_timing_upload_cannot_fail_the_weekly_sweep—always()if-no-files-found: ignore.test_validation_notebook_timing_upload_keeps_the_no_notebooks_gate.test_validation_timing_dataset_keeps_full_default_retention— noretention-dayskey.test_timing_artifacts_stay_out_of_the_aggregate_namespace—analyzestill downloads
pattern: results-*, and neither new name starts withresults-.Key Files
.github/workflows/workspace-validation.yml— the weekly sweep's body; thetwo new upload steps.
.github/workflows/smoke-tests.yml— the PR gate. Untouched; itssmoke-timings-<python-version>upload is the pattern being mirrored.tests/test_workflow_wiring.py— the wiring pins.autohands/result_collector.py—RunReport.write(), theproducer of
smoke_timings.json. Not modified.Trade-offs
ingester by widening its contract to glob
results-*too. That contractdoes not exist yet (nothing in PyAutoHeart references
smoke-timingsoutsidesmoke-tests.ymland its tests), so (b) means writing a promise instead of afix, and leaves the timings expiring at the
results-*30-day retention. (a)is also symmetric with the PR gate, which is the shape a future ingester
wants.
results-*payload. Keeping them apartcosts one extra upload step per leg but keeps the two consumer contracts
clean —
aggregate_results.pyglobs**/*.jsonunderresults-*andexcludes the sidecar by name; feeding it a second copy is the wrong
direction.
mode=releasealso gets the upload.run_scriptsruns in both modes andthe step is unconditional, so a release-integration run publishes its timings
too. Harmless and arguably useful; the project set is a subset there, so no
new collision risk.
Testing
python3 -m pytest tests/— full Heart suite, on the branch.workspace-smoke.ymlrun (or amanual
workflow_dispatchof it): the artifact list should gainsmoke-timings-scripts-*/smoke-timings-notebooks-*entries with noduplicate-name failures across the ~50 legs.
Original Prompt
Click to expand starting prompt
The weekly smoke run's timings land in
results-*under no discoverable nameType: maintenance
Target: pyautoheart
Repos:
Difficulty: small
Autonomy: safe
Priority: low
Status: draft
Filed: 2026-08-24
Follow-on gap from
complete/2026/08/smoke-timings-dataset.md(PyAutoHandsd2a22f4+ PyAutoHeart#1673df42b5, merged 2026-08-24). All facts belowverified 2026-08-24 via the Actions API.
The gap
RunReport.write()now emitssmoke_timings.json(schemasmoke_timings/1)in every report dir, but the named upload
(
smoke-timings-<python-version>,if: always(),if-no-files-found: ignore)was added only to the reusable PR-gate workflow
@PyAutoHeart/.github/workflows/smoke-tests.yml.The weekly
workspace-smoke.ymlrun goes through@PyAutoHeart/.github/workflows/workspace-validation.yml, which has no suchnamed upload — e.g. run
32688412626(2026-08-24 04:00 UTC, success,136 jobs).
Its
run_scripts/generate_notebookslegs do emit the file now. Verifieddifferentially: the
results-scripts-autolens-imagingartifact grew from2 files / 3,950 bytes (2026-08-19, run
32277952488) to 3 files /5,306 bytes (2026-08-24, PyAutoHands at
d2a22f4). So the weekly timingdata — by far the widest sample the organism produces — persists only inside
the per-leg
results-*zips, under no name a consumer can glob for.Task
Pick one and record the decision in the PR body:
workspace-validation.yml's script andnotebook legs (
if: always(),if-no-files-found: ignore). Simpler andsymmetric with the PR gate — preferred unless (b) proves cheaper.
ingester (the follow-up named in
complete/2026/08/smoke-timings-dataset.md)to globbing
smoke_timings.jsonout ofresults-*artifacts as well assmoke-timings-*ones, and say so in that contract.Trap for (a): artifact-name collisions. The PR gate has one leg per python
version, so a single fixed name is safe there. The weekly run has ~50+ legs in
one run, so a fixed
smoke-timings-<python-version>would collide — the namemust carry the leg's package/directory (mirroring how
results-*is alreadydisambiguated, e.g.
results-scripts-autolens-imaging).Acceptance
smoke-timings-*name, or the ingester contract explicitly coversresults-*— not left implicit either way.smoke-tests.ymluntouched, or provablyequivalent).
workspace-validation.ymlstays green; no artifact-name collisions in afull weekly run.