Skip to content

Release v0.30.7.6: Claude mid-turn Steer (#2008) and 9 new audited carries - #30

Merged
swear01 merged 0 commit into
mainfrom
release/v0.30.7.6
Oct 24, 2026
Merged

swear01 merged 0 commit into
mainfrom
release/v0.30.7.6

Conversation

@swear01

@swear01 swear01 commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Same official base as v0.30.7.5 (5d8deb20). Rebuilt from the audited v0.30.7.5 queue; publication replaces main with --force-with-lease against 6c3e23c4.

Changes vs v0.30.7.5

Added after re-inventory (2026-10-23)

Operator-selected PRs, replayed at their audited heads:

Swear Review triage (d58ec32, 25 findings)

Verification (oracle, 2026-10-23)

  • bun typecheck, bun run test: CLI 3,656 / 19 skip, hub 1,642, web 3,846, shared 346, relay 118, desktop 5; 0 failures. Frozen install: no changes.
  • Rehearsal: REPLAY_TREE_MATCHES=47dee910, REHEARSAL_OK; the replayed tree passes the same suites. Maintenance tests: 43 pass. gen:fixtures: no drift.
  • e2e (120s timeout): the CI list plus load-earlier-running-session, luna-reserve, session-mention-drag, jump-navigation and session-title-lock, 58 pass after the luna-reserve fix. session-read-sync needs a live hub and was not run.
  • CLI runner integration not rerun: on this host the 10s runner-start wait fails on the v0.30.7.5 baseline too.

Release record: tools/maintenance/releases/v0.30.7.6/.

🤖 Generated with Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-22T05:59:01.607201Z
We couldn’t complete this review. Comment @codex review to try again.
d58ec32 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@cursor

cursor Bot commented Oct 22, 2026

Copy link
Copy Markdown

This PR is too large for Bugbot to review. It changes 72,056 lines and 4,043,672 characters. Split the change into smaller pull requests to get a review.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new HAPI Desktop launcher, which uses Electron to manage the HAPI Hub and Runner by launching a bundled platform-specific CLI binary. It also includes significant updates to the Codex session management, specifically adding support for mid-turn steering and conversation rewinding, along with various improvements to Android text paging, permission modes, and WxPusher notification support. My review identified a potential runtime error in the claudeRemote.ts cleanup logic where response.waitForExit() could be called on an undefined object if initialization fails, and I have provided a fix for this.

Comment on lines +568 to +573
if (pendingSteers.size > 0) {
// An early return leaves the process running, and it could still
// run these prompts while their restored copies run again.
processController.abort();
await response.waitForExit();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In the finally block, response.waitForExit() is called if pendingSteers.size > 0. However, if an error occurs during initialization (for example, inside applyInitialTurn or before response is assigned), response will be undefined. Since pendingSteers can be populated as soon as setTurnActive(true) is called (which happens inside applyInitialTurn), pendingSteers.size > 0 can be true while response is still undefined. This will result in a TypeError: Cannot read properties of undefined (reading 'waitForExit') (or a ReferenceError if block-scoped), masking the original initialization error.

Please add a safety check to ensure response is defined before calling waitForExit().

Suggested change
if (pendingSteers.size > 0) {
// An early return leaves the process running, and it could still
// run these prompts while their restored copies run again.
processController.abort();
await response.waitForExit();
}
if (pendingSteers.size > 0 && response) {
// An early return leaves the process running, and it could still
// run these prompts while their restored copies run again.
processController.abort();
await response.waitForExit();
}

@swear-review swear-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swear Review — Full PR review · batch 1/1 · OCR v1.13.2 · deepseek-flash

cd ../release-artifacts
sign-macos:
if: github.repository == 'swear01/hapi'
runs-on: macos-14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the jobs in this pipeline declare timeout-minutes. The sign-macos steps run security import / codesign, which can block indefinitely waiting for keychain unlock or an interactive prompt. Without a timeout a hung job can occupy a runner until the (default) hard limit, delaying the whole multi-stage release. Consider adding timeout-minutes to each job.

Suggested change
runs-on: macos-14
runs-on: macos-14
timeout-minutes: 60

low · maintainability

— Swear Review

Comment on lines 38 to +40
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oven-sh/setup-bun is a third-party action pinned to a mutable tag (@v2). A moved/retagged release would run arbitrary code with this job's token, so it should be pinned to a full commit SHA (same applies to the other oven-sh/setup-bun@v2 occurrences in this file and in fixtures.yml).

Suggested change
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
- uses: oven-sh/setup-bun@<commit-sha> # v2.x.y
with:
bun-version: 1.4.0

low · security

— Swear Review

Comment on lines 22 to +23
build:
if: github.repository == 'tiann/hapi'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting the repository guard on build (and not only on deploy) makes the whole workflow a no-op on this fork (release.yml targets swear01/hapi), so the web production build (bun run --cwd web build) is no longer validated anywhere in CI here. If the intent is only to avoid deploying GitHub Pages from the fork, consider guarding just deploy; otherwise please confirm dropping web build coverage is intended.

Suggested change
build:
if: github.repository == 'tiann/hapi'
build:
runs-on: ubuntu-latest

low · maintainability

— Swear Review

with open(output, "w") as handle:
handle.write("upstream_ref\tauthor\thead_sha\tdecision\tstatus\tmaintainer_signal\tbot_verdict\tbot_sha\tscope\tbehavior\treason\n")
for pr in json.load(sys.stdin):
author = pr["author"]["login"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

author is a nullable GraphQL field (GitHub returns author: null for PRs opened by deleted/ghost accounts). Since this loops over every open PR in a long-lived repo, a single such PR raises TypeError here, the pipe fails and set -euo pipefail aborts the whole audit, after audit.md has already been written (leaving a partial release record).

Suggested change
author = pr["author"]["login"]
author = (pr.get("author") or {}).get("login") or "ghost"

medium · bug

— Swear Review

pkill -KILL -f "$SMOKE_PATTERN" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This SIGKILLs every process whose command line matches the broad SMOKE_PATTERN, not just this harness's children. The startup stray check only rejects processes that existed before the run, so a developer's own bun hub/src/index.ts / bun cli/src/index.ts runner start-sync (started after the check, or on a shared box with HAPI_SMOKE_FORCE=1) is hard-killed on exit, losing their session state. Prefer killing only this run's descendants instead of a global pattern match.

Suggested change
pkill -KILL -f "$SMOKE_PATTERN" 2>/dev/null || true
pkill -KILL -P "$HUB_PID" 2>/dev/null || true
pkill -KILL -P "$RUNNER_PID" 2>/dev/null || true

medium · bug

— Swear Review

Comment on lines +168 to +169
await this.rewindBarrier.load();
this.rewindBarrier.assertOpen();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SharedRewindBarrier.load() sets the token to the 'reconciliation-required' sentinel whenever the rewind file exists, and assertOpen() then throws, so prepare() fails for any session with a leftover rewind file. Because CompleteConversationRewind is only registered in registerControls() (called from activate(), which runs after bind()/prepare()), a root that fails prepare() never registers the recovery handler. The hub also only calls completeConversationRewind after a successful rewind(), so a crash (or a failed complete RPC) with the barrier file on disk leaves the session permanently unresumable — only manual deletion of the file recovers it. Note rewind.test.ts even asserts a replacement barrier cannot complete() the persisted state. Consider letting startup proceed in a blocked-but-recoverable state (register controls, then accept CompleteConversationRewind), or persisting/restoring the real token so the hub can reconcile in-band.

high · bug

— Swear Review

Comment thread cli/src/codex/shared/rewind.ts Outdated
}
/** Only before native dispatch: rejection is proven, so no reconciliation is needed. */
async cancel(): Promise<void> {
await unlink(this.file); this.token = undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel() awaits unlink before clearing this.token. If the unlink rejects (e.g. the file was already removed, or an FS error), this.token stays set, leaving the barrier permanently blocked even though the rejection was proven, and the throw propagates out of rewind()'s catch — turning the intended rejected(...) result into an RPC-level error. Clear the token first and tolerate a missing file:

this.token = undefined;
await unlink(this.file).catch(() => {});

medium · bug

— Swear Review

Comment on lines -413 to +477
if (this.closed || this.stopping || this.reconnecting || !this.threadId) return;
if (this.closed || this.stopping || this.reconnecting || !this.threadId || this.rewindBarrier.blocked) return;
// The new connection replays authoritative pending native requests.
this.deferredRequests = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completeRewind discards buffered native server requests (this.deferredRequests = []) without replaying them, whereas the rejection path in rewind() explicitly calls replayDeferredRequests(). Any native server request (e.g. an approval prompt) that arrived while the barrier was closed is silently dropped, and the app-server may then wait indefinitely for a response that is never sent. replayDeferredRequests()'s own comment claims replay is safe because retired requests are ignored — please reuse it here (or document why dropping is correct after a successful revert).

medium · bug

— Swear Review

Comment on lines +284 to +285
...(sandbox ? { permissionMode: sandbox === 'dangerFullAccess' ? 'yolo' : sandbox === 'readOnly' ? 'read-only'
: this.settingsNative.approvalsReviewer === 'auto_review' ? 'autoReview' : 'default' } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a three-level nested ternary, which the project checklist forbids (hard to read and easy to mis-edit when a mode is added). Extract a helper, e.g. permissionModeFromSandbox(sandbox, approvalsReviewer):

...(sandbox ? { permissionMode: permissionModeFromSandbox(sandbox, this.settingsNative.approvalsReviewer) } : {}),

low · style

— Swear Review

send(downstream, { id: message.id, result: await options.hooks.control(message.method, message.params) });
return;
}
release = await options.hooks.admit?.(message, connectionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

admit can now reject id-less notifications (runtime.ts throws 'Native mutations require a request ID'), which lands in the catch below. That branch always sends { id: message.id, error }; for a notification message.id is undefined, so JSON.stringify emits a malformed {"error":...} frame with no id, and the notification is dropped instead of forwarded upstream. Only emit the error envelope when message.id !== undefined (otherwise just drop/report the notification).

medium · bug

— Swear Review

@swear-review

swear-review Bot commented Oct 22, 2026

Copy link
Copy Markdown

Swear Review

Mode: Full PR
Model: deepseek-flash
OCR: v1.13.2
Commit: d58ec32

Findings:

  • Maintainability: 8
  • Security: 1
  • Bug: 13
  • Performance: 1
  • Documentation: 1
  • Style: 1

25 findings total
25 inline comments

Status: ❌ Failed
OCR review incomplete: status=partial, exit=0, failed items=63

@swear01
swear01 force-pushed the release/v0.30.7.6 branch 2 times, most recently from 1276614 to af29626 Compare October 23, 2026 20:19
@swear01
swear01 merged commit af29626 into main Oct 24, 2026
2 of 5 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