can1357/oh-my-pi · error

GPT-5 Harmony leak persisted after ${harmonyRetryAttempt} re

Error message

GPT-5 Harmony leak persisted after ${harmonyRetryAttempt} retries (${signalListLabel(err.detection.signals)}).

What it means

The agent loop detects GPT-5 'Harmony' template leaks (raw harmony channel tokens like <|channel|> appearing in model output). It first tries abort-and-retry (up to 2 times) and truncate-and-resume recovery; if the leak still persists after the retry budget is exhausted, the loop throws this error instead of letting corrupted assistant output through. It is a deliberate escalation so callers fail loudly rather than consume leaked internal-format text.

Source

Thrown at packages/agent/src/agent-loop.ts:1289

					if (err.recovered) {
						if (harmonyTruncateResumeCount >= 2) {
							await emitHarmonyAudit(config, err, "escalated", harmonyRetryAttempt);
							throw new Error(
								`GPT-5 Harmony leak recurred after truncate-and-resume recovery (${signalListLabel(err.detection.signals)}).`,
							);
						}
						harmonyTruncateResumeCount++;
						recovered = err.recovered;
						message = recovered.message;
						await emitHarmonyAudit(config, err, "truncate_resume", harmonyRetryAttempt);
						// A recovered message completes the turn, so the abort-retry counter
						// resets like the normal success path (the truncate-resume counter
						// keeps accumulating for its cross-turn cap).
						harmonyRetryAttempt = 0;
					} else {
						if (harmonyRetryAttempt >= 2) {
							await emitHarmonyAudit(config, err, "escalated", harmonyRetryAttempt);
							throw new Error(
								`GPT-5 Harmony leak persisted after ${harmonyRetryAttempt} retries (${signalListLabel(err.detection.signals)}).`,
							);
						}
						await emitHarmonyAudit(config, err, "abort_retry", harmonyRetryAttempt);
						harmonyRetryAttempt++;
						continue;
					}
				}
				if (recovered) {
					message = snapshotAssistantMessage(message);
					currentContext.messages.push(message);
					stream.push({ type: "message_start", message: snapshotAssistantMessage(message) });
					stream.push({ type: "message_end", message: snapshotAssistantMessage(message) });
				}
				newMessages.push(message);

				// The escalation choice (if any) applied to the call above; clear it so
				// only the single escalation turn carries the forced choice.

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the request later or switch to a different model/endpoint — repeated leaks usually indicate a provider-side regression, not a bug in your code
  2. Verify the model is being served through the correct harmony-dialect route (check PI_DIALECT / provider routing config); a non-harmony endpoint serving a harmony model leaks tokens
  3. Clear or shorten the conversation (compact/truncate history) — long or adversarial contexts increase leak probability
  4. Check provider status/changelog for a harmony template change and pin a known-good model revision

Example fix

// before: same corrupted session retried manually, still fails
await agent.prompt("continue"); // throws: Harmony leak persisted after 2 retries
// after: switch model or start a fresh session
agent.setModel("claude-opus-4"); // or agent.newSession() to drop the poisoned context
await agent.prompt("continue");
Defensive patterns

Strategy: retry

Validate before calling

// Check model routing before starting a harmony-sensitive session
const model = agent.getModel();
if (model && /gpt-5/i.test(model.id) && Bun.env.PI_DIALECT !== "harmony") {
  console.warn("gpt-5 model without harmony dialect routing: leak risk");
}

Type guard

function isHarmonyLeakError(err: unknown): err is Error & { message: string } {
  return err instanceof Error && err.message.includes("Harmony leak persisted");
}

Try / catch

try {
  await agent.prompt(userInput);
} catch (err) {
  if (isHarmonyLeakError(err)) {
    // provider-side serialization problem: do NOT retry in a tight loop
    logger.error("harmony leak persisted", { signals: err.message });
    await compactOrRestartSession(agent); // drop poisoned context / switch model
  } else throw err;
}

Prevention

When it happens

Trigger: A streaming model response raises HarmonyLeakInterruption with recovered=false, and harmonyRetryAttempt is already >= 2 — i.e. three consecutive model responses from a GPT-5 harmony-dialect model leaked harmony control tokens and neither in-stream truncation nor re-issuing the request cleaned them up.

Common situations: Running GPT-5/gpt-5-codex-class models via a provider or proxy that mangles the harmony chat template (wrong dialect routing, custom base URL, patched sampler); very long contexts or unusual system prompts that push the model into emitting raw channel markers; provider-side incidents where the harmony serialization changes.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/b69a1c7f790d963d. Report an issue: GitHub.