Skip to content

fix(ci): gate version bump on CI, atomic releases, PTY session tests - #9

Merged
ohernandezdev merged 2 commits into
mainfrom
ci/hardening
Jul 27, 2026
Merged

fix(ci): gate version bump on CI, atomic releases, PTY session tests#9
ohernandezdev merged 2 commits into
mainfrom
ci/hardening

Conversation

@ohernandezdev

Copy link
Copy Markdown
Owner

Summary

Implements the 3-item CI hardening goal from this session:

  1. Compile-gate the version bump. version-bump.yml used to fire on push: branches: [main], the same event as CI, racing it instead of waiting — a commit that failed to compile could still get bumped, tagged, and dispatched to release.yml, which would only then fail, after the tag was already public. Switched the trigger to workflow_run: workflows: ["CI"], gated on conclusion == 'success' and head_branch == 'main', and pinned every git operation to workflow_run.head_sha. Added a concurrency group so overlapping runs queue instead of racing.
  2. Atomic multi-platform releases. release.yml published each platform's installers live as soon as that leg finished. If one platform failed (e.g. macOS notarization) after the others succeeded, users could already be downloading a release missing a platform. Set releaseDraft: true and added a publish job gated on needs.release.result == 'success' that flips the release public only once every leg has succeeded; on any failure the draft is left in place for inspection, never deleted or partially published.
  3. PTY session unit tests. Added 4 Rust tests for the session bookkeeping from the earlier stability-audit PR (kill_pty on an unknown id, kill_pty actually killing/removing a real session, replacing a reused id reaps the old child, and the current/superseded generation checks). Extracted the logic into small testable functions with no behavior change. cargo test was already wired into the existing CI job/matrix — nothing new to add there.

Notable finding during implementation

Spawning two real PTYs in one test process measured 80+ seconds on this Windows host (ConPTY/conhost teardown apparently serializes) — the "replace a session" test now keeps only one side real and uses a fake Child/MasterPty for the other. This required adding anyhow as a direct dev-dependency (portable-pty's traits return anyhow::Error but don't re-export it publicly; it's already resolved transitively, so no new crate enters the tree).

Test plan

  • All 14 Rust tests pass (10 pre-existing + 4 new), cargo test --locked
  • cargo check --locked clean, no new warnings
  • npm run build clean
  • Both edited workflow files validated with a real YAML parser (js-yaml) — the exact bug class that broke version-bump.yml's first real run this session
  • End-to-end: once merged, this commit (fix(ci): ...) should itself trigger a real patch bump via the new workflow_run-gated path — watching for that after merge
  • End-to-end: a future release with an intentionally-failing platform leg should confirm the release stays draft (not yet exercised — would require a deliberate failure injection)

🤖 Generated with Claude Code

https://claude.ai/code/session_01PioEvgnoP4fpxDrSAYTBdF

ohernandezdev and others added 2 commits July 27, 2026 02:08
…add PTY session unit tests

Three gaps identified after today's incidents (a broken version-bump.yml
syntax error, and a false-positive major-version bump from the bot's own
commit message):

- version-bump.yml raced CI instead of waiting for it (both fired on the
  same push). A commit that failed to compile could still get bumped,
  tagged, and dispatched to release.yml, which would only then fail —
  after the tag was already public. Switched its trigger from
  `push: branches: [main]` to `workflow_run: workflows: ["CI"]`, gated on
  `conclusion == 'success'`, and pinned every git operation to
  workflow_run.head_sha instead of whatever main's tip happens to be.
  Added a concurrency group so overlapping runs queue instead of racing.

- release.yml published each platform's installers to a live, public
  release as soon as that platform's job finished — if e.g. Windows and
  Linux succeeded but macOS notarization failed, users could already be
  downloading (and the updater pointing at) a release missing a platform
  entirely. Set releaseDraft: true and added a `publish` job that only
  flips the release public once every matrix leg succeeded
  (needs.release.result == 'success'); on any failure the draft is left
  as-is for inspection, never deleted, never partially published.

- Added 4 Rust unit tests for the PTY session bookkeeping introduced in
  the stability-audit PR (kill_pty on an unknown id, kill_pty actually
  killing/removing a real session, insert_session reaping a replaced
  session, and the session_is_current/session_is_superseded generation
  checks). Extracted the logic those commands relied on into small
  testable functions (kill_pty_impl, insert_session,
  session_is_current/superseded) with no behavior change. Two real PTYs
  overlapping in one test process measured 80s+ on this Windows host
  (ConPTY/conhost teardown serializing) — the replace-session test keeps
  only one side real and uses a fake Child/MasterPty for the other, which
  also required adding anyhow as a direct dev-dependency (portable-pty's
  Child/MasterPty traits return anyhow::Error but don't re-export it
  publicly; it's already resolved transitively). cargo test was already
  wired into the existing CI job/matrix — no separate workflow needed.

All 14 tests (10 pre-existing + 4 new) pass; cargo check --locked and
npm run build both clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PioEvgnoP4fpxDrSAYTBdF
…TY spawning from tests entirely

CI (ubuntu/macos) failed the previous commit: the #[cfg(unix)] mock
methods referenced libc::pid_t, matching portable-pty's own trait
signature, but libc — like anyhow before it — is only a transitive
dependency, not directly nameable without its own Cargo.toml entry.

While fixing that, re-ran the suite locally and hit real, host-dependent
flakiness: a single real PTY session's Drop (closing its ConPTY/conhost
handle) took anywhere from under a second to 60+ seconds on this
machine, on different tests each run. That's the same class of
"synchronous close can block for a while" risk `reap()` exists to keep
off the async runtime for a session's child — but here it was the
master/writer half, undeferred, blocking the test thread itself.

Rather than fight that timing, dropped the real-PTY test helper
entirely; all 4 pty_session_tests now use only the fake Child/MasterPty,
making them fast (0.00s) and deterministic. Left a comment flagging the
master/writer-drop-can-block-the-async-runtime pattern as a candidate
for a future stability pass — out of scope for this PR.

All 14 tests pass locally; cargo check --locked clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PioEvgnoP4fpxDrSAYTBdF
@ohernandezdev
ohernandezdev merged commit b885a06 into main Jul 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant