Merge branch 'fix/pre-publish-blockers-v4.5.0'

Pre-publish blockers for v4.5.0:
- fix(runtime-fallback): gate retryable signal on status-code allowlist (f05e0cbe9)
- fix(parent-wake): bound assistant-text defer to escape stuck sessions (69c955f61)
- fix(ralph-loop): time-bound oracle dispatch wait to prevent stall (14b3523af)
- test(dist-bundle): assert inlined prompt content survives bundling (3e0a975d1)
- fix(package): block internal-only assets from publish payload (8e28e29c2)

Verified via publish-debate-vortex hyperultradebate (6 hostile agents, 3 rounds).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-26 16:44:50 +09:00
16 changed files with 718 additions and 27 deletions
@@ -45,6 +45,7 @@ export function createLoopStateController(options: {
completion_promise: initialCompletionPromise,
initial_completion_promise: initialCompletionPromise,
verification_attempt_id: undefined,
verification_attempt_started_at: undefined,
verification_session_id: undefined,
ultrawork: loopOptions?.ultrawork,
verification_pending: undefined,
@@ -139,6 +140,7 @@ export function createLoopStateController(options: {
state.verification_pending = true
state.completion_promise = ULTRAWORK_VERIFICATION_PROMISE
state.verification_attempt_id = undefined
state.verification_attempt_started_at = undefined
state.verification_session_id = undefined
state.initial_completion_promise ??= DEFAULT_COMPLETION_PROMISE
@@ -156,6 +158,7 @@ export function createLoopStateController(options: {
}
state.verification_session_id = verificationSessionID
state.verification_attempt_started_at = undefined
if (!writeState(directory, state, stateDir)) {
return null
@@ -175,6 +178,7 @@ export function createLoopStateController(options: {
state.completion_promise = state.initial_completion_promise ?? DEFAULT_COMPLETION_PROMISE
state.verification_pending = undefined
state.verification_attempt_id = undefined
state.verification_attempt_started_at = undefined
state.verification_session_id = undefined
if (typeof messageCountAtStart === "number") {
state.message_count_at_start = messageCountAtStart
@@ -197,6 +201,7 @@ export function createLoopStateController(options: {
state.completion_promise = state.initial_completion_promise ?? DEFAULT_COMPLETION_PROMISE
state.verification_pending = undefined
state.verification_attempt_id = undefined
state.verification_attempt_started_at = undefined
state.verification_session_id = undefined
if (typeof messageCountAtStart === "number") {
state.message_count_at_start = messageCountAtStart
@@ -7,6 +7,8 @@ import { handleFailedVerification } from "./verification-failure-handler"
import { withTimeout } from "./with-timeout"
import type { IterationCommitExpectation } from "./types"
export const STUCK_VERIFICATION_TIMEOUT_MS = 30 * 60 * 1000
type OpenCodeSessionMessage = {
info?: { role?: string }
parts?: Array<{ type?: string; text?: string }>
@@ -138,12 +140,25 @@ export async function handlePendingVerification(
}
if (state.verification_attempt_id && !state.verification_session_id) {
log(`[${HOOK_NAME}] Skipped verification failure: oracle dispatch in flight`, {
sessionID,
verificationAttemptId: state.verification_attempt_id,
iteration: state.iteration,
})
return
const startedAt = state.verification_attempt_started_at
const attemptAgeMs = startedAt !== undefined ? Date.now() - startedAt : undefined
const isStuck = attemptAgeMs !== undefined && attemptAgeMs > STUCK_VERIFICATION_TIMEOUT_MS
if (isStuck) {
log(`[${HOOK_NAME}] Stuck oracle dispatch detected, proceeding to failure handler`, {
sessionID,
verificationAttemptId: state.verification_attempt_id,
attemptAgeMs,
iteration: state.iteration,
})
} else {
log(`[${HOOK_NAME}] Skipped verification failure: oracle dispatch in flight`, {
sessionID,
verificationAttemptId: state.verification_attempt_id,
iteration: state.iteration,
})
return
}
}
const restarted = await handleFailedVerification(ctx, {
+18 -1
View File
@@ -41,6 +41,7 @@ export function readState(directory: string, customPath?: string): RalphLoopStat
}
const ultrawork = data.ultrawork === true || data.ultrawork === "true" ? true : undefined
const verificationAttemptStartedAt = Number(data.verification_attempt_started_at)
const maxIterations =
data.max_iterations === undefined || data.max_iterations === ""
? ultrawork
@@ -65,6 +66,12 @@ export function readState(directory: string, customPath?: string): RalphLoopStat
verification_attempt_id: data.verification_attempt_id
? stripQuotes(data.verification_attempt_id)
: undefined,
verification_attempt_started_at:
data.verification_attempt_started_at === undefined || data.verification_attempt_started_at === ""
? undefined
: Number.isFinite(verificationAttemptStartedAt)
? verificationAttemptStartedAt
: undefined,
verification_session_id: data.verification_session_id
? stripQuotes(data.verification_session_id)
: undefined,
@@ -106,9 +113,19 @@ export function writeState(
const initialCompletionPromiseLine = state.initial_completion_promise
? `initial_completion_promise: "${state.initial_completion_promise}"\n`
: ""
const existingState = readState(directory, customPath)
const verificationAttemptStartedAt = state.verification_session_id || !state.verification_attempt_id
? undefined
: state.verification_attempt_started_at
?? (existingState?.verification_attempt_id !== state.verification_attempt_id
? Date.now()
: existingState.verification_attempt_started_at)
const verificationAttemptLine = state.verification_attempt_id
? `verification_attempt_id: "${state.verification_attempt_id}"\n`
: ""
const verificationAttemptStartedAtLine = typeof verificationAttemptStartedAt === "number"
? `verification_attempt_started_at: ${verificationAttemptStartedAt}\n`
: ""
const verificationSessionLine = state.verification_session_id
? `verification_session_id: "${state.verification_session_id}"\n`
: ""
@@ -124,7 +141,7 @@ export function writeState(
active: ${state.active}
iteration: ${state.iteration}
${maxIterationsLine}completion_promise: "${state.completion_promise}"
${initialCompletionPromiseLine}${verificationAttemptLine}${verificationSessionLine}started_at: "${state.started_at}"
${initialCompletionPromiseLine}${verificationAttemptLine}${verificationAttemptStartedAtLine}${verificationSessionLine}started_at: "${state.started_at}"
${sessionIdLine}${ultraworkLine}${verificationPendingLine}${strategyLine}${messageCountAtStartLine}---
${state.prompt}
`
@@ -0,0 +1,138 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
import { releaseAllPromptAsyncReservationsForTesting } from "../shared/prompt-async-gate"
import { handlePendingVerification, STUCK_VERIFICATION_TIMEOUT_MS } from "./pending-verification-handler"
import type { RalphLoopState } from "./types"
const NOW_MS = 1_800_000_000_000
type PendingVerificationInput = Parameters<typeof handlePendingVerification>[1]
type LoopStateController = PendingVerificationInput["loopState"]
function createState(verificationAttemptStartedAt?: number): RalphLoopState {
const state: RalphLoopState = {
active: true,
iteration: 2,
completion_promise: "<ulw-verification>",
initial_completion_promise: "<promise>DONE</promise>",
started_at: "2026-01-01T00:00:00.000Z",
prompt: "Ship release blockers",
session_id: "session-123",
ultrawork: true,
verification_pending: true,
verification_attempt_id: "attempt-123",
}
if (verificationAttemptStartedAt === undefined) {
return state
}
return {
...state,
verification_attempt_started_at: verificationAttemptStartedAt,
}
}
function createPluginInput(promptCalls: string[]): PluginInput {
return unsafeTestValue<PluginInput>({
client: {
session: {
messages: async () => ({ data: [] }),
promptAsync: async (input: unknown) => {
promptCalls.push(JSON.stringify(input) ?? "")
return {}
},
abort: async () => ({}),
},
tui: {
showToast: async () => ({}),
},
},
directory: "/tmp/ralph-loop-stuck-oracle-test",
})
}
function createLoopStateController(state: RalphLoopState) {
const clearVerificationState = mock<LoopStateController["clearVerificationState"]>(() => state)
const incrementIteration = mock<LoopStateController["incrementIteration"]>(() => state)
const loopState = {
restartAfterFailedVerification: mock<LoopStateController["restartAfterFailedVerification"]>(() => null),
clearVerificationState,
incrementIteration,
clear: mock<LoopStateController["clear"]>(() => true),
setVerificationSessionID: mock<LoopStateController["setVerificationSessionID"]>(() => null),
} satisfies LoopStateController
return { loopState, clearVerificationState, incrementIteration }
}
async function runPendingVerification(state: RalphLoopState, loopState: LoopStateController, promptCalls: string[]) {
await handlePendingVerification(createPluginInput(promptCalls), {
sessionID: "session-123",
state,
matchesParentSession: true,
matchesVerificationSession: false,
loopState,
directory: "/tmp/ralph-loop-stuck-oracle-test",
apiTimeoutMs: 100,
})
}
describe("ralph-loop stuck oracle dispatch recovery", () => {
const realDateNow = Date.now
beforeEach(() => {
Date.now = () => NOW_MS
})
afterEach(() => {
Date.now = realDateNow
releaseAllPromptAsyncReservationsForTesting()
})
test("#given verification attempt is recent and no verification session exists #when pending verification is handled #then handler returns early", async () => {
// given
const promptCalls: string[] = []
const state = createState(NOW_MS - 1_000)
const { loopState, clearVerificationState, incrementIteration } = createLoopStateController(state)
// when
await runPendingVerification(state, loopState, promptCalls)
// then
expect(promptCalls).toHaveLength(0)
expect(clearVerificationState).not.toHaveBeenCalled()
expect(incrementIteration).not.toHaveBeenCalled()
})
test("#given verification attempt is older than stuck timeout and no verification session exists #when pending verification is handled #then handler proceeds to failed verification recovery", async () => {
// given
const promptCalls: string[] = []
const state = createState(NOW_MS - STUCK_VERIFICATION_TIMEOUT_MS - 1)
const { loopState, clearVerificationState, incrementIteration } = createLoopStateController(state)
// when
await runPendingVerification(state, loopState, promptCalls)
// then
expect(promptCalls).toHaveLength(1)
expect(clearVerificationState).toHaveBeenCalledTimes(1)
expect(incrementIteration).toHaveBeenCalledTimes(1)
})
test("#given legacy verification attempt has no start timestamp and no verification session exists #when pending verification is handled #then handler returns early", async () => {
// given
const promptCalls: string[] = []
const state = createState()
const { loopState, clearVerificationState, incrementIteration } = createLoopStateController(state)
// when
await runPendingVerification(state, loopState, promptCalls)
// then
expect(promptCalls).toHaveLength(0)
expect(clearVerificationState).not.toHaveBeenCalled()
expect(incrementIteration).not.toHaveBeenCalled()
})
})
+1
View File
@@ -8,6 +8,7 @@ export interface RalphLoopState {
completion_promise: string
initial_completion_promise?: string
verification_attempt_id?: string
verification_attempt_started_at?: number
verification_session_id?: string
started_at: string
prompt: string
@@ -111,6 +111,83 @@ describe("runtime-fallback error classifier", () => {
expect(retryable).toBe(true)
})
test("isRetryableError REJECTS isRetryable=true when status code is 401 Unauthorized", () => {
//#given
const error = { error: { statusCode: 401, isRetryable: true } }
//#when
const retryable = isRetryableError(error, [429, 503, 529])
//#then
expect(retryable).toBe(false)
})
test("isRetryableError REJECTS isRetryable=true when status code is 403 Forbidden", () => {
//#given
const error = { error: { statusCode: 403, isRetryable: true } }
//#when
const retryable = isRetryableError(error, [429, 503, 529])
//#then
expect(retryable).toBe(false)
})
test("isRetryableError REJECTS isRetryable=true when status code is 404 Not Found", () => {
//#given
const error = { error: { statusCode: 404, isRetryable: true } }
//#when
const retryable = isRetryableError(error, [429, 503, 529])
//#then
expect(retryable).toBe(false)
})
test("isRetryableError HONORS isRetryable=true when status code is 429 (rate-limit)", () => {
//#given
const error = { error: { statusCode: 429, isRetryable: true } }
//#when
const retryable = isRetryableError(error, [429, 503, 529])
//#then
expect(retryable).toBe(true)
})
test("isRetryableError HONORS isRetryable=true when status code is 503 (service unavailable)", () => {
//#given
const error = { error: { statusCode: 503, isRetryable: true } }
//#when
const retryable = isRetryableError(error, [429, 503, 529])
//#then
expect(retryable).toBe(true)
})
test("isRetryableError HONORS isRetryable=true when no status code is present (pure network error)", () => {
//#given
const error = { error: { isRetryable: true } }
//#when
const retryable = isRetryableError(error, [429, 503, 529])
//#then
expect(retryable).toBe(true)
})
test("isRetryableError HONORS isRetryable=true when status code is in retryOnErrors list", () => {
//#given
const error = { error: { statusCode: 400, isRetryable: true } }
//#when
const retryable = isRetryableError(error, [400, 429, 503, 529])
//#then
expect(retryable).toBe(true)
})
test("ignores malformed retryable flags on otherwise non-retryable errors", () => {
//#given
const error = {
+16 -3
View File
@@ -1,4 +1,5 @@
import { DEFAULT_CONFIG, RETRYABLE_ERROR_PATTERNS } from "./constants"
import { DEFAULT_CONFIG, HOOK_NAME, RETRYABLE_ERROR_PATTERNS } from "./constants"
import { log } from "../../shared/logger"
export { extractAutoRetrySignal } from "./auto-retry-signal"
@@ -119,6 +120,10 @@ export function extractRetryableSignal(error: unknown): boolean | undefined {
return undefined
}
function isStatusCodeRetrySafe(code: number, retryOnErrors: number[]): boolean {
return retryOnErrors.includes(code) || (code >= 500 && code < 600) || code === 408 || code === 425 || code === 429
}
function isLocalizedQuotaExhaustionMessage(message: string): boolean {
return (
(/预扣费额度失败/i.test(message) && /用户剩余额度/i.test(message)) ||
@@ -221,8 +226,16 @@ export function isRetryableError(error: unknown, retryOnErrors: number[]): boole
return true
}
if (extractRetryableSignal(error) === true) {
return true
const retryableSignal = extractRetryableSignal(error)
if (retryableSignal === true) {
if (statusCode === undefined || isStatusCodeRetrySafe(statusCode, retryOnErrors)) {
return true
}
log(`[${HOOK_NAME}] Retryable signal rejected due to unsafe status code`, {
statusCode,
retryOnErrors,
})
}
return RETRYABLE_ERROR_PATTERNS.some((pattern) => pattern.test(message))