Release v0.29.0.6: native session control and fleet maintenance - #17
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces extensive updates to the HAPI CLI and agent runner, including a comprehensive suite of peer session control commands, support for session-attached jobs, platform-scoped Agy hook carriers with liveness-based sweeps, native rewind/resume capabilities for Claude and Codex, and durable upgrade delegation for runner restarts. The reviewer identified several critical issues where synchronous operations (such as 'execFileSync' and synchronous file system calls) block the Node.js event loop in asynchronous contexts, which can freeze active sessions in multi-session runner environments. Additionally, a missing rejection handler in 'AcpSdkBackend.ts' was flagged as a potential cause for unhandled promise rejections.
I am having trouble creating individual review comments. Click here to see my feedback.
cli/src/agent/backends/acp/AcpSdkBackend.ts (1323)
If queuedUpdates rejects, calling .then(() => true) without a rejection handler will cause the promise returned by Promise.race to reject. This can propagate out of waitForQueueSettled and potentially crash the prompt turn or cause an unhandled promise rejection. Consider appending a .catch() or providing a second argument to .then() to handle rejections gracefully.
queuedUpdates.then(() => true, () => false),
cli/src/claude/runClaude.ts (278-284)
Using execFileSync synchronously blocks the Node.js event loop for up to 10 seconds if the command hangs or runs slowly. In a multi-session runner environment, this will freeze all other active sessions on the same runner. Since runClaude is already an asynchronous function, consider using the asynchronous execFile (promisified) to avoid blocking the event loop.
let claudeVersionOutput: string | null = null;
try {
const { execFile } = await import('node:child_process');
const { promisify } = await import('node:util');
const execFileAsync = promisify(execFile);
const { stdout } = await execFileAsync(getDefaultClaudeCodePath(), ['--version'], {
timeout: 10_000
});
claudeVersionOutput = stdout.trim();
} catch (error) {cli/src/codex/utils/codexSessionScanner.ts (71-125)
Using synchronous file system operations (statSync, openSync, readSync, closeSync) inside an asynchronous function blocks the Node.js event loop. In a multi-session runner, this can cause significant latency and freeze other active sessions while performing disk I/O. Since node:fs/promises is already imported, please use the asynchronous stat, open, and file handle read operations instead.
let handle;
try {
const fileStats = await stat(transcriptPath);
const size = fileStats.size;
if (size <= 0) return [];
handle = await open(transcriptPath, 'r');
let position = size;
const scanStart = Math.max(0, size - maxBytes);
let incompletePrefix = Buffer.alloc(0);
while (position > scanStart && !replayUsageComplete(accumulator)) {
const length = Math.min(position - scanStart, readChunkBytes);
position -= length;
const buffer = Buffer.alloc(length);
const { bytesRead } = await handle.read(buffer, 0, length, position);
const combined = Buffer.concat([buffer.subarray(0, bytesRead), incompletePrefix]);
let complete = combined;
if (position > scanStart) {
const firstNewline = combined.indexOf(0x0a);
if (firstNewline < 0) {
incompletePrefix = combined;
continue;
}
incompletePrefix = combined.subarray(0, firstNewline);
complete = combined.subarray(firstNewline + 1);
} else {
incompletePrefix = Buffer.alloc(0);
}
const lines = complete.toString('utf8').split(/\r?\n/);
// Newest lines are at the end of this chunk window.
for (let index = lines.length - 1; index >= 0; index -= 1) {
noteUsageFromTranscriptLine(lines[index] ?? '', accumulator, threadId);
if (replayUsageComplete(accumulator)) break;
}
}
if (
scanStart === 0
&& incompletePrefix.length > 0
&& !replayUsageComplete(accumulator)
) {
noteUsageFromTranscriptLine(incompletePrefix.toString('utf8'), accumulator, threadId);
}
return orderedReplayUsagePayloads(accumulator).reverse();
} catch (error) {
logger.debug(`[codex-session-scanner] Failed to reverse-scan usage from ${transcriptPath}: ${error}`);
return [];
} finally {
if (handle !== undefined) {
try { await handle.close(); } catch { /* ignore */ }
}
}|
Swear Review is still running after a 20-minute wait on a82cb24. Recording this as a review-wait timeout and proceeding with the configured fallback; this does not claim the remote job failed or cancel it. Context for the latest-head review: the PR now has the previous maintained release 7a89dee as an ancestor, and the reviewed change is that release to a82cb24. All CI checks for the source have passed. I checked the earlier Gemini observations against callers: both assignments to AcpSdkBackend.sessionUpdateQueue attach rejection handlers; the queue is initialized resolved. Claude sessions run in separate detached processes, so the startup version probe does not block other runner sessions. The Codex usage tail scan is bounded to 4 MiB in 64 KiB reads. Please assess any remaining actionable correctness issues against the actual current diff and these call paths. |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request replaces HAPI-owned prompt injection with a bundled native hapi-session-runtime skill and introduces deterministic JSON CLI commands for exact-ID session control (such as spawn-peer, wait-peer, stop-peer, and archive-peer). It also removes the old sessionSummaryContract prompt setting and updates SQLite deletion cascades. One high-severity issue was identified in the skill installation logic, where an empty pre-existing directory can trigger a false-positive "Refusing to overwrite user-managed skill" error and block session startup.
|
The empty-directory startup failure is fixed in b09df51. The new regression failed before the fix; all 19 native-skill tests now pass, including preserving a nonempty unmanaged directory without SKILL.md. The implementation claims only a new or empty directory while holding the existing cross-process lock, and preserves all symlink and foreign-content guards. Fresh verification passed: full typecheck; all 8,205 unit tests (16 existing skips, Web run with two workers under local load); Runner integration 14 passed/1 skipped; maintenance checks 42/42; four-patch replay matches the tested source tree. Latest-head CI is running. Swear Review's previous full run was explicitly cancelled by the service as timed out at 2026-09-05 18:26:55 UTC after its 45-minute hard limit. Its automatic new run remains separate; continuing with the configured Gemini fallback for the current head. |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request upgrades HAPI to version 0.29.0.6, introducing a prompt-free session control workflow. It replaces HAPI-owned prompt injection with a bundled native hapi-session-runtime skill and deterministic JSON CLI commands. It adds new exact-ID peer commands (such as spawn-peer, wait-peer, and ping-peer) and corresponding MCP tools, enforcing exact UUID boundaries and rejecting prefix-based matching. Additionally, it introduces atomic session spawning and remit delivery with automatic compensating cleanup on failure, while removing the plan permission mode from Grok and OpenCode. The review comments correctly identify a discrepancy where the custom UUID validation regexes (EXACT_SESSION_ID_RE and UUID_RE) are more restrictive than standard Zod UUID validation, which could cause valid UUIDs of other versions or variants to be rejected at runtime.
|
The latest Gemini findings assert that UUIDv3/v5 and nonstandard variants pass the pinned Zod validator but fail the runtime regex. I verified this against the installed Zod from cli/: UUIDv3 and UUIDv5 both pass both validators; a non-RFC variant and version 9 both fail both validators. The prior empty-directory finding is fixed in b09df51, with regression coverage. All latest-head CI checks have now passed. Gemini's terminal review still contains the disproven UUID findings, so proceeding to the next configured provider under the OR review policy. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b09df5181a
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex acknowledged the latest-head review request but has not produced a terminal result after 20 minutes; that request remains pending. The Cursor Bugbot dashboard redirects to login, so repository enablement cannot be verified and Bugbot was not triggered. Rechecking the available Gemini review with the concrete UUID counterexamples already recorded above. For b09df51, the exact installed-Zod/runtime results are:
Both runtime regexes accept versions 1 through 8 and RFC variants 8/9/a/b, matching the pinned validator for these normal UUIDs. Please reassess the reported mismatch against the actual installed Zod behavior and review any remaining actionable correctness issues in the current PR. All latest-head CI and the native-skill recovery regressions have passed. |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request upgrades HAPI to version 0.29.0.6 and introduces a prompt-free session control workflow via the new hapi-session-runtime skill, removing HAPI-owned prompt injections across all agent adapters. It adds several new CLI commands and MCP tools for atomic session spawning, waiting, inspecting, and lifecycle management (stop, archive, delete) using exact UUIDs. Additionally, a bulk 'mark all as read' action has been added to the web UI. Feedback is provided regarding a potential security and directory mismatch issue in spawnSessionWithRemit where worktree base paths are not strictly validated against the requested directory.
|
Review fixes are now on 92ff3e0:
Fresh full typecheck and all package tests passed: 8216 passed, 16 existing skips, zero failures. Runner integration 14 passed/1 skipped; browser 17/17; maintenance 42/42. The five-patch replay matches canonical source tree 5eb42eab259638f9711b326db9c9be16d0bed62b. Production remains unchanged. The previous-head Swear Review reached terminal timeout; requesting Gemini re-review of the fixes and final candidate. |
|
/gemini review |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Gemini returned its terminal daily-quota limit. Proceeding to Codex on current head f964e71. This head only corrects the post-test artifact fingerprint in the release audit; executable source and canonical tree remain those validated above. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f964e71216
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Fixed both latest Codex findings in this head: preserve generated remit IDs for intermediary HTTP 502/504 and all post-spawn failure paths; derive effort support from the shared capability table, covering Kimi/Copilot. The 20 spawn tests and fresh full typecheck/package gate pass (8218 passed, 16 existing skips, zero failures). Six-patch replay matches c4acd769ed5d00b7acaa0760a244e7952943ee15. Please re-review the final candidate; Gemini remains quota-limited and prior Swear runs timed out. |
|
@codex review |
|
Both latest findings are fixed: invoked localId retries validate the payload and return success without re-emitting to the CLI; uninvoked retries still deliver. A restarted MessageService regression failed before the fix and now passes. Kimi/Copilot effort guidance is aligned in the canonical skill, CLI help, and README. Fresh full typecheck/package gate: 8225 passed, 16 existing skips, zero failures; eight-patch source replay matches e8c6b84cbbea5bd28ffed83499e963e31d5aa5ec. Requesting latest-head re-review. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 184f356f17
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| message: z.string().min(1).describe('Required first user message'), | ||
| name: z.string().trim().min(1).max(SESSION_NAME_MAX_LENGTH).optional().describe('Session display name'), | ||
| machineId: z.string().trim().min(1).optional().describe('Exact runner machine id'), | ||
| agent: z.enum(CREATABLE_AGENT_FLAVORS as unknown as [string, ...string[]]).optional() |
There was a problem hiding this comment.
z.enum(CREATABLE_AGENT_FLAVORS as unknown as [string, ...string[]]) uses an unsafe double cast. CREATABLE_AGENT_FLAVORS is typed as readonly Exclude<AgentFlavor, 'gemini'>[] (a computed filter result), not a literal tuple, so the cast is needed to satisfy the zod enum API — but it silently discards the enumeration values at the type level while still working at runtime since zod accepts a readonly array. This is identical to the established pattern used in startHappyServer.ts, but here the cast crosses through an intermediate type that isn't assignable, so a cleaner alternative is z.enum([...CREATABLE_AGENT_FLAVORS] as [string, ...string[]]) or importing AgentFlavorSchema/defining a dedicated tuple to keep the allowed values type-checked.
low · maintainability
— Swear Review
| agent: z.enum(CREATABLE_AGENT_FLAVORS as unknown as [string, ...string[]]).optional() | ||
| .describe('Agent flavor; defaults to claude'), |
There was a problem hiding this comment.
spawn_peer is newly added to DEFAULT_TOOL_NAMES, but the underlying spawnPeer request is routed through the session's own runner auth (same machineId/directory as this session by default) and agent defaults to 'claude'. If codex itself is the current session, registering a tool whose default agent is 'claude' is inconsistent with the current runtime. More importantly, this bridge is used by non-codex flavors too (it's the generic STDIO bridge invoked via commands/mcp.ts), so at minimum the description should state the default explicitly. The description text 'defaults to claude' is the only source of truth here since the schema is passed through without defaults.
medium · documentation
— Swear Review
| ? JSON.stringify({ ok: false, error: { code, message } }) | ||
| : `${chalk.red('hapi machines:')} ${message}` | ||
| useJson ? console.log(output) : console.error(output) | ||
| process.exit(error instanceof TokenInitializationError ? 2 : error instanceof SpawnPeerError ? exitCodeForSpawnPeerError(error) : 1) |
There was a problem hiding this comment.
Nested ternary expression. Per the code-quality rules, nested ternaries are not allowed; they hurt readability. Extract the exit code with an if/else chain (or a helper) and pass it to process.exit.
| process.exit(error instanceof TokenInitializationError ? 2 : error instanceof SpawnPeerError ? exitCodeForSpawnPeerError(error) : 1) | |
| let exitCode = 1 | |
| if (error instanceof TokenInitializationError) exitCode = 2 | |
| else if (error instanceof SpawnPeerError) exitCode = exitCodeForSpawnPeerError(error) | |
| process.exit(exitCode) |
low · style
— Swear Review
| agent: args.agent as Parameters<typeof spawnPeer>[0]['agent'], | ||
| model: args.model, | ||
| effort: args.effort, | ||
| sessionType: args.sessionType, | ||
| permissionMode: args.permissionMode as Parameters<typeof spawnPeer>[0]['permissionMode'], |
There was a problem hiding this comment.
The handler args type agent?: string / permissionMode?: string forces these unsafe casts to satisfy spawnPeer's types. The MCP SDK validates args against the zod inputSchema (and spawnPeer re-validates both fields), so the cast is semantically safe today, but it bypasses compiler checks — if the schema is ever loosened or a future caller skips validation, an arbitrary string would be passed straight through to spawnPeer. Consider typing the handler parameters with z.infer-based types (or narrowing to AgentFlavor / PermissionMode) so the casts become unnecessary and the compiler enforces the contract.
| agent: args.agent as Parameters<typeof spawnPeer>[0]['agent'], | |
| model: args.model, | |
| effort: args.effort, | |
| sessionType: args.sessionType, | |
| permissionMode: args.permissionMode as Parameters<typeof spawnPeer>[0]['permissionMode'], | |
| agent: args.agent ?? 'claude', | |
| model: args.model, | |
| effort: args.effort, | |
| sessionType: args.sessionType, | |
| permissionMode: args.permissionMode, |
medium · maintainability
— Swear Review
| message: z.string().min(1).describe('Required first user message'), | ||
| name: z.string().trim().min(1).max(SESSION_NAME_MAX_LENGTH).optional().describe('Session display name'), | ||
| machineId: z.string().trim().min(1).optional().describe('Exact runner machine id'), | ||
| agent: z.enum(CREATABLE_AGENT_FLAVORS as unknown as [string, ...string[]]).optional() |
There was a problem hiding this comment.
z.enum(CREATABLE_AGENT_FLAVORS as unknown as [string, ...string[]]) relies on a double cast (as unknown as) to satisfy Zod's non-empty-tuple typing. It works only because the runtime filter() result currently has 10 entries. If CREATABLE_AGENT_FLAVORS is ever emptied or widened, z.enum([]) throws at server startup or silently accepts values spawnPeer doesn't support (it would then fail with bad_args). Prefer deriving the schema from a typed non-empty tuple or a z.enum factory that validates the array at construction, and let the compiler check the element type instead of casting to string[].
| agent: z.enum(CREATABLE_AGENT_FLAVORS as unknown as [string, ...string[]]).optional() | |
| agent: z.enum([...CREATABLE_AGENT_FLAVORS] as [Exclude<AgentFlavor, 'gemini'>, ...Exclude<AgentFlavor, 'gemini'>[]]).optional() |
low · maintainability
— Swear Review
| { | ||
| type: 'text' as const, | ||
| text: formatInspectPeerReport(result), | ||
| text: `Spawned ${result.sessionId} remit=${result.remitId} (${result.name})`, |
There was a problem hiding this comment.
Success text uses remit= while error text uses remitId= (same for the ping_peer handler). The model reads both to reason about idempotency retries; inconsistent labels make output parsing ambiguous. Use remitId= consistently in both success and error messages.
| text: `Spawned ${result.sessionId} remit=${result.remitId} (${result.name})`, | |
| text: `Spawned ${result.sessionId} remitId=${result.remitId} (${result.name})`, |
low · style
— Swear Review
| if (error instanceof PingPeerError || error instanceof TokenInitializationError) { | ||
| const output = commandArgs.includes('--json') |
There was a problem hiding this comment.
The catch block decides JSON vs. text output using commandArgs.includes('--json'), the raw argument list, rather than the parsed result. Side effects: (1) format/routing logic is duplicated four times and can silently diverge; (2) a malformed invocation like hapi inspect-peer --limit --json — where --json appears as the (consumed) value for --limit in parseInspectPeerArgs — still triggers JSON output even though the user never successfully passed a --json flag, which is confusing for scripts. Better: capture the parsed json flag out of handleInspectPeerCommand (e.g. re-parse args in the catch, or have the handler annotate the parsed args) and route on that single value consistently, as machines.ts does with const useJson = json || commandArgs.includes('--json').
| if (error instanceof PingPeerError || error instanceof TokenInitializationError) { | |
| const output = commandArgs.includes('--json') | |
| if (error instanceof PingPeerError || error instanceof TokenInitializationError) { | |
| const useJson = commandArgs.includes('--json') && !commandArgs.includes('--limit') |
medium · bug
— Swear Review
| } else if (arg.startsWith('--message-file=')) { | ||
| result.messageFile = arg.slice('--message-file='.length) |
There was a problem hiding this comment.
The --message-file=<value> and --remit-id=<value> forms skip the - prefix guard applied to the space-separated forms. So hapi ping-peer <id> --message-file=-foo is accepted as a literal path while ... --message-file -foo errors out with 'requires a path or -'. Align the validation across both syntaxes so parsing behavior is consistent regardless of which flag form the user picks.
| } else if (arg.startsWith('--message-file=')) { | |
| result.messageFile = arg.slice('--message-file='.length) | |
| } else if (arg.startsWith('--message-file=')) { | |
| const value = arg.slice('--message-file='.length) | |
| if (!value || (value.startsWith('-') && value !== '-')) throw new PingPeerError('bad_args', '--message-file requires a path or -') | |
| result.messageFile = value |
low · maintainability
— Swear Review
| error: { code: error.code, message: error.message } | ||
| }) | ||
| : `${chalk.red('hapi ping-peer:')} ${error.message}` | ||
| commandArgs.includes('--json') ? console.log(output) : console.error(output) |
There was a problem hiding this comment.
A ternary is used as a statement purely for its side effects (? console.log(output) : console.error(output)), discarding the expression result, and this pattern is duplicated in both error branches. Since process.exit immediately follows, output routing is easy to miss. An explicit if/else would make the stdout-vs-stderr decision clearer and easier to maintain.
| commandArgs.includes('--json') ? console.log(output) : console.error(output) | |
| if (commandArgs.includes('--json')) { | |
| console.log(output) | |
| } else { | |
| console.error(output) | |
| } |
low · style
— Swear Review
| if (result.messageFile !== undefined && !result.messageFile) { | ||
| throw new PingPeerError('bad_args', '--message-file requires a path or -') | ||
| } | ||
| if (result.waitActiveSecs !== undefined && (!Number.isFinite(result.waitActiveSecs) || result.waitActiveSecs <= 0 || result.waitActiveSecs > 300)) { |
There was a problem hiding this comment.
This change introduces a new 300-second upper bound for --wait and HAPI_WAIT_ACTIVE_SECS, but the updated help text does not document the option or the 1–300s range at all. Users who previously relied on larger wait values will now get unexpected validation errors without guidance on the new limit; the range should be mentioned in the usage/help output.
low · documentation
— Swear Review
Swear ReviewMode: Full PR Findings:
10 findings total Status: ✅ Completed |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05c1182ba6
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7812c02ec7
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff95684105
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
/swear-review incremental |
|
/swear-review status |
|
Swear Review — status Current head SHA: |
|
/gemini review |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request bumps HAPI to version 0.29.0.6 and introduces a prompt-free session control workflow. It adds a suite of new peer-session control commands (such as spawn-peer, wait-peer, and inspect-peer) and installs a bundled native hapi-session-runtime skill on the execution host. Additionally, it removes the previously integrated 'session-attached jobs' (hapi job) functionality and HAPI-owned prompt injection prose, refines permission modes for Grok and OpenCode, and ensures HTML app shells are never cached. The feedback identifies a potential ENOENT error when attempting to acquire a file lock on a directory that does not yet exist during a fresh installation, and suggests creating the target directory before invoking the lock.
| await mkdir(skillRoot, { recursive: true, mode: 0o700 }) | ||
| await withSettingsFileLock(targetDir, async () => { | ||
| const targetDirStat = await lstat(targetDir).catch(() => null) |
There was a problem hiding this comment.
The withSettingsFileLock function is called with targetDir as the lock target before targetDir is guaranteed to exist (it is created inside the lock callback on line 109). Depending on the underlying file-locking implementation, this can throw an ENOENT error on a fresh installation where the directory does not yet exist. Creating targetDir beforehand ensures the lock can be acquired safely.
await mkdir(skillRoot, { recursive: true, mode: 0o700 })
await mkdir(targetDir, { recursive: true, mode: 0o700 }).catch(() => null)
await withSettingsFileLock(targetDir, async () => {
const targetDirStat = await lstat(targetDir).catch(() => null)|
Operator authorized publishing v0.29.0.6 and deploying the fleet after reviewing the external-review status. Exact release head: cc45252. All eight mechanical CI checks passed. Swear Review timed out; Gemini reviewed this head and its sole target-directory ENOENT finding was checked against withSettingsFileLock (realpath: false, sibling .hapi.lock) and all 20 runtime-skill installation tests passed. No code change was needed. This is operator authorization to proceed, not a claim of an unconditional clean bot result. |
Rebuild maintained HAPI v0.29.0.6 from upstream
980a921ba15665c54998a6ddb658103d467ff4cb. The fresh 162-PR audit selects 58 carries, 100 deferrals, and 4 drops.Compared with v0.29.0.5, this integrates native skill and atomic session control (tiann#1771), cascade-safe group deletion (tiann#1607), rewind window preservation (tiann#1766), inactive-notice alignment (tiann#1770), confirmed current-device mark-all-read (tiann#1773), and stable sidebar viewport (tiann#1776). The bundled
hapi-session-runtimeskill coexists with user-managedhapi-session-controlinstallations. Existing upgrade generations, workspace guards, and permission boundaries remain enforced.Integration fixes cover interrupted and same-version skill installation, delayed runtime readiness, worktree containment, fresh-session validation, effort capabilities, and remit retry/result consistency. Completed remits are never redelivered; ambiguous spawn failures retain their retry ID; only a new reservation with a proven undispatched RPC can bypass process cleanup. Codex cumulative snapshots return one final answer per stream. Reused remit IDs with conflicting payloads return HTTP 409; operational spawn failures retain HTTP 502. Remit polling retries transient failures within its deadline and rejects crashed partial results. Bulk mark-as-read reports local storage failures through the existing confirmation dialog. Same-batch user acknowledgments do not end remit results. HTML app shells consistently use no-store headers in source and compiled Hub serving modes.
Validation: fresh full typecheck and all package tests after excluding tiann#1424 (8,138 passed, 16 existing skips, zero failures); Playwright scroll/terminal/composer 17/17; maintenance tests 42/42; fixture regeneration leaves tracked fixtures unchanged. The fourteen-patch queue reproduces source tree
354d69d4e8b9ae94a5dd6cd0ade2276a55387f96. CLI/Web suites use two workers with unchanged test timeouts. Earlier integration gates passed Runner 14/1 skipped and Swift 627 tests; latest-head CI rechecks platform builds and screenshot baselines.Personal-policy exceptions: tiann#1662/tiann#1436
maintainer-policy-exception; tiann#1468status-checks-failed, maintainer-policy-exception. tiann#1771's 151-file scope is mapped in the audit and its upstream latest-head HAPI Bot review is clean.Release metadata:
tools/maintenance/releases/v0.29.0.6/; curated notes:.github/release-notes/v0.29.0.6.md. After exact-head review and all CI pass, publish this exact SHA to fork main using the captured lease, wait for main CI, then tag and deploy. No production service has been changed yet.Temporarily exclude upstream tiann#1424 at the operator’s request because stale running jobs pin inactive sessions indefinitely. Remove the job CLI/API/UI and restore active-session pinning, retaining schema V29 and legacy data for a safe upgrade.
Retain the independent CLI argument-normalization and Cursor ambient-thinking fixes previously bundled with tiann#1424; removing them reproduced two unrelated regressions. The Jobs feature remains excluded.
Latest head
cc45252848ba0033d1927e24c1121f821eb5b68d: all eight mechanical CI checks passed. External review remains unmet: Gemini reports daily quota exhaustion (comment 5557178312); Codex reports review usage exhaustion (5555175596); Cursor dashboard requires login, so repository enablement cannot be verified. Earlier Swear runs repeatedly timed out; the b111788 incremental run remained pending for over 30 minutes before the final CLI fix superseded it. The current automatic full review is still pending. No tag, Release, main update, or production deployment has been performed.