URL: https://github.com/can1357/oh-my-pi/issues/2834
Content-Type: text/markdown
Method: github-issue
Notes: Fetched via GitHub API

---

# Question: is there an OMP equivalent to Claude Code Stop hook semantics?

**#2834** · closed · opened by @cexll
Created: 2026-06-17T04:15:40Z · Updated: 2026-06-17T09:35:13Z
Labels: question, agent, triaged

---

## Question

Is there an OMP extension event that is semantically equivalent to Claude Code's `Stop` hook for the **main agent only**?

Claude Code `Stop` runs when the main agent has finished responding, before the turn is allowed to stop. It can return `decision: "block"` or `hookSpecificOutput.additionalContext` so Claude continues the conversation with that feedback. It also exposes loop protections such as `stop_hook_active` and an 8-consecutive-block cap.

I am trying to implement a mission/proof guard in OMP with the same behavior:

1. When the main agent is about to stop, inspect mission status.
2. If the mission is not complete, inject the current mission status / next action as prompt context.
3. Continue the same session so the main agent keeps working.
4. Do not trigger this behavior for task/subagent completion.
5. Avoid infinite continuation loops.

## What I found

- `session_shutdown` does not appear equivalent. It is teardown-time, fire-and-forget, and its result is discarded. Issue #2600 also describes shutdown handlers as best-effort with a short timeout, so this does not seem suitable for blocking stop or continuing work.
- `before_agent_start` can inject prompt/system context before a model turn, but it is pre-turn, not stop-time.
- `agent_end` looks closer to Claude Code `Stop`, but it appears to fire for subagent sessions too. The task executor forwards parent `preloadedExtensionPaths` into subagent sessions and initializes an extension runner there, so a global extension listening to `agent_end` would also run on task/subagent completion.
- #1433 discussed Claude Code hook compatibility but was closed as not planned, and the proposed `Stop -> session_shutdown` mapping seems semantically different from Claude Code `Stop`.
- #2796 / #2798 fixed discovered hook factory loading, but that seems to address discovery/execution, not Stop-equivalent control semantics.

## Ask

What is the recommended OMP-native way to implement Claude Code `Stop` semantics?

Specifically:

- Is `agent_end` intended to be used as the main-agent Stop-equivalent?
- If yes, is there a supported way to tell main-agent `agent_end` apart from subagent `agent_end` in the extension context?
- Is there an existing continuation API/pattern equivalent to `Stop` returning `decision: "block"` or `additionalContext`?
- If not, would OMP consider adding a main-agent-only event such as `main_agent_stop` / `before_agent_stop`, with built-in loop protection similar to Claude Code's `stop_hook_active` and 8-block cap?

## Desired behavior

A native event roughly like:

```ts
pi.on("main_agent_stop", async (event, ctx) => {
  const status = await checkMissionStatus(ctx.cwd);
  if (status.ready) return;

  return {
    continue: true,
    additionalContext: status.nextAction,
  };
});
```

or an officially documented extension-runner pattern that achieves the same result without catching subagent stops.

---

## Comments (7)

### @roboomp · 2026-06-17T04:35:04Z

Short answer: there is not a 1:1 OMP equivalent to Claude Code `Stop` for **main agent only** today.

What exists now:

- `agent_end` is the post-agent-loop event. It is emitted after an `AgentSession` receives core `agent_end` (`packages/coding-agent/src/session/agent-session.ts:3516-3524`) and its payload is only `{ type, messages }` (`packages/coding-agent/src/extensibility/shared-events.ts:178-182`). It is useful as a post-turn hook, but it is not typed as a blocking/decision hook.
- `agent_end` handler return values are ignored by the generic extension runner; only `session_before_*` and `session.compacting` results are collected there (`packages/coding-agent/src/extensibility/extensions/runner.ts:589-634`). So `return { decision: "block" }` / `additionalContext` is not a supported contract on `agent_end`.
- `session_shutdown` is intentionally teardown-only: it uses the short shutdown timeout and discards results (`packages/coding-agent/src/extensibility/extensions/runner.ts:74-93`, `:593-604`). Your read of it is correct.
- The continuation primitive is `pi.sendMessage(message, { deliverAs: "nextTurn", triggerTurn: true })`. The public docs describe this as: `nextTurn` stores/injects on the next prompt, and `triggerTurn: true` starts a turn when idle / schedules an internal continuation while streaming (`docs/extensions.md:134-142`). Core uses that pattern in `autoresearch` (`packages/coding-agent/src/autoresearch/index.ts:257-291`) and autolearn adds its own suppression flag to avoid re-triggering on the synthetic turn (`packages/coding-agent/src/autolearn/controller.ts:83-133`).

Main-agent vs subagent:

- There is no supported `ctx.isMainAgent`, session-kind, or parent-agent field in `ExtensionContext` today. The exposed context is `ui`, `cwd`, read-only `sessionManager`, model state, idle/pending helpers, etc. (`packages/coding-agent/src/extensibility/extensions/types.ts:323-354`; `docs/extensions.md:145-160`).
- Subagents do receive the parent’s discovered extension paths and re-load them into their own session (`packages/coding-agent/src/task/index.ts:1294-1296`, `packages/coding-agent/src/task/executor.ts:1995-2006`, `packages/coding-agent/src/sdk.ts:1800-1828`). That means a global extension listening to `agent_end` should expect subagent `agent_end` too.

Recommended OMP-native pattern today, if you can tolerate all sessions:

```ts
let continuationActive = false;
let consecutiveContinuations = 0;

pi.on("agent_end", async (_event, ctx) => {
  if (continuationActive) {
    continuationActive = false;
    return;
  }
  if (ctx.hasPendingMessages()) return;

  const status = await checkMissionStatus(ctx.cwd);
  if (status.ready) {
    consecutiveContinuations = 0;
    return;
  }
  if (consecutiveContinuations >= 8) return;

  consecutiveContinuations++;
  continuationActive = true;
  pi.sendMessage(
    {
      customType: "mission-guard",
      content: status.nextAction,
      display: false,
      attribution: "agent",
    },
    { deliverAs: "nextTurn", triggerTurn: true },
  );
});
```

Caveat: that is a convention, not Claude `Stop` parity. It is not main-agent-only, and loop protection is extension-owned.

For exact Claude `Stop` semantics, I think the right OMP API would be a new first-class stop/settle event: main-session scoped, awaited before the turn is allowed to settle, with a structured result such as `{ continue: true, additionalContext }`, plus built-in reentrancy and consecutive-block caps. `main_agent_stop` / `before_agent_stop` would be reasonable names; the important part is the contract, not the name.

---
If this didn't solve your issue, react 👎 on this comment and I'll keep it open.
Otherwise I'll auto-close in 4 hours.

---

### @cexll · 2026-06-17T05:25:01Z

Follow-up after checking the current hook surfaces in both Codex and Claude Code docs:

- OpenAI Codex hooks docs list these turn-scoped events as core: `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`, `UserPromptSubmit`, `SubagentStop`, and `Stop`. `SessionStart` and `SubagentStart` are separate thread/subagent-start scoped events.
- Codex `Stop` is explicitly a main-turn stop hook: `decision: "block"` continues by creating a continuation prompt from the reason. It also has `stop_hook_active`, `last_assistant_message`, and `turn_id`.
- Codex `SubagentStop` is separate and has `agent_id`, `agent_type`, `agent_transcript_path`, `stop_hook_active`, and `last_assistant_message`.
- Claude Code docs expose the same important distinction: `Stop` runs when the main agent finishes responding and can continue the conversation with `decision: "block"` or `hookSpecificOutput.additionalContext`; `SubagentStop` is separate; `SessionEnd` is teardown-only and cannot block termination.

Given that both major hook APIs distinguish main-agent `Stop` from subagent stop and session teardown, I think OMP should add an explicit first-class event rather than ask extensions to infer it from generic `agent_end`.

Concrete request:

1. Add a main-session-only stop event, preferably named `session_stop` or `main_agent_stop` / `before_agent_stop`.
   - Fires when the main agent is about to settle/stop for the current turn.
   - Does not fire for task/subagent completion.
   - Does not fire for process teardown; `session_shutdown` remains cleanup-only.

2. Give it Claude/Codex-style continuation control:

```ts
pi.on("session_stop", async (event, ctx) => {
  return {
    continue: true,
    additionalContext: "Run one more pass before stopping.",
  };
});
```

or an equivalent command-hook-compatible result shape:

```json
{
  "decision": "block",
  "reason": "Mission is not complete; continue with this next action."
}
```

3. Include loop/reentrancy protection in the event contract:
   - `stop_hook_active` or equivalent flag.
   - a built-in consecutive continuation cap, matching Claude's 8-block cap or a documented OMP-specific cap.

4. Include enough input fields for reliable policy hooks:
   - `turn_id`
   - `last_assistant_message`
   - `background_tasks` / pending task summary if available
   - session id / transcript path if available
   - an explicit session kind such as `main` so hooks do not need to inspect session entries.

5. Keep `SubagentStop` separate if OMP wants subagent continuation semantics:
   - `subagent_stop` with `agent_id`, `agent_type`, `agent_transcript_path`, `stop_hook_active`, etc.

Why this matters: mission/proof guards, validation gates, and policy checks need exactly the same behavior Codex/Claude `Stop` provide: when the main agent says it is done but the external gate says it is not, inject feedback and continue the current session. `session_shutdown` cannot do this, and generic `agent_end` is too broad because it also sees subagent sessions and ignores handler return values.

So the gap is not just naming. The missing contract is: **main-agent turn stop, awaited before settle, with continuation feedback and loop protection**.

---

### @roboomp · 2026-06-17T05:25:43Z

Agreed: this is a missing contract, not just an event-name mismatch.

Restating the requested OMP API: add a main-session stop hook that runs after the main agent has produced its final assistant message for a turn, before the session settles, and lets an extension return continuation feedback. It must be distinct from `session_shutdown` teardown and from subagent completion.

Feasibility looks good. OMP already has the pieces:

- main sessions already defer wire-level `agent_end` until prompt unwind, so there is a clear settle boundary to hook before;
- extension messages can already continue a session via `sendMessage(..., { deliverAs: "nextTurn", triggerTurn: true })`;
- built-ins already implement loop suppression patterns for auto-continuations.

The missing work is making that a first-class extension result contract instead of an extension-owned convention.

Tradeoffs / design points maintainers should decide before implementation:

1. Event name: I would prefer `session_stop` if it is explicitly main-session-only; `before_agent_stop` if it needs to read as a cancelable pre-event.
2. Result shape: OMP-native `{ continue: true, additionalContext }` is cleaner, but a Claude/Codex-compatible `{ decision: "block", reason }` adapter may be useful for hook portability.
3. Cap semantics: match Claude’s 8 consecutive blocks unless OMP has a reason to choose a different documented default.
4. Payload size: `last_assistant_message`, `turn_id`, session id/file, and `stop_hook_active` are straightforward; `background_tasks` needs a stable summary contract so hooks do not depend on UI/transcript internals.
5. Subagents: keep separate as `subagent_stop` if added; do not overload `session_stop` with session-kind checks.

I’m not opening a PR from this thread because this is now an unaccepted enhancement/proposal, not a bug fix. If a maintainer marks it accepted or says “go ahead”, I can implement the first-class event plus docs/tests on this branch.

---
If this didn't solve your issue, react 👎 on this comment and I'll keep it open.
Otherwise I'll auto-close in 4 hours.

---

### @cexll · 2026-06-17T05:29:23Z

One more parity pass against Codex/Claude hook docs, specifically for common daily hook events:

| Codex / Claude event | Closest OMP extension event today | Gap |
| --- | --- | --- |
| `SessionStart` | `session_start`, `before_agent_start` | OMP can start/inject, but `session_start` has no start source like `startup` / `resume` / `clear` / `compact`. Prompt injection currently belongs in `before_agent_start`, not `session_start`. |
| `UserPromptSubmit` | `input`, `turn_start`, `before_agent_start` | No direct event with submitted prompt plus Claude/Codex-style block/additionalContext semantics. `input` can replace/handle text, but it is not the same developer-context hook contract. |
| `PreToolUse` | `tool_call` | OMP can block with `{ block, reason }`, but lacks the fuller Codex/Claude shape for `additionalContext`, `updatedInput`, matcher aliases, and command-hook-compatible payload. |
| `PermissionRequest` | `tool_approval_requested` / `tool_approval_resolved` | OMP exposes notification-style approval events, but I do not see a handler result contract to allow/deny/ask like Codex `PermissionRequest`. |
| `PostToolUse` | `tool_result` | OMP can modify tool result content/details, but lacks a documented `decision: block` / additional feedback contract like Codex `PostToolUse` continuation. |
| `PreCompact` | `session_before_compact`, `session.compacting` | Mostly present. `session_before_compact` can cancel or provide compaction; `session.compacting` can add summary context/prompt/preserveData. Naming and payload differ from Codex/Claude but the lifecycle exists. |
| `PostCompact` | `session_compact` | Event exists after compaction, but handler return values are ignored. Codex `PostCompact` supports common output fields such as `continue: false`; OMP `session_compact` is notification-only. |
| `SubagentStart` | generic subagent `session_start` / `agent_start` inside subagent session | No first-class `subagent_start` event with `agent_id` / `agent_type` fields. |
| `SubagentStop` | generic subagent `agent_end` | No first-class `subagent_stop` event with continuation contract, `agent_id`, `agent_type`, transcript path, and `stop_hook_active`. |
| `Stop` | generic `agent_end` | Missing first-class main-agent-only stop event with awaited continuation result and built-in loop protection. |
| `SessionEnd` | `session_shutdown` | Exists, correctly teardown-only. Should stay separate from Stop/session_stop semantics. |

So `PostCompact` specifically: OMP does have `session_compact`, but it is not equivalent to Codex/Claude `PostCompact` if users expect command-hook-style output handling (`continue: false`, feedback, common hook result shape). It is currently best understood as an after-compaction notification event.

Request: if OMP wants closer compatibility with Codex/Claude daily hooks, please consider adding/documenting these first-class events/contracts, not only aliases:

1. `session_stop` / `main_agent_stop` — main-agent-only, awaited before settle, supports continuation feedback.
2. `subagent_start` and `subagent_stop` — explicit subagent lifecycle with agent metadata and optional subagent continuation.
3. `user_prompt_submit` — prompt-submit hook with block/additionalContext semantics.
4. `permission_request` — approval hook with allow/deny/decline result semantics.
5. `post_compact` result semantics — either make `session_compact` support common result fields or add an alias with documented behavior.
6. Normalize result shapes for `pre_tool_use` / `post_tool_use` equivalents so extensions can add model-visible context, rewrite supported tool input where safe, or continue from hook feedback.

The important gap is not just event names; it is whether the event is awaited at the correct point and whether its result participates in the agent loop.

---

### @cexll · 2026-06-17T05:31:25Z

Thanks, that matches what I need.

Please go ahead with the first-class implementation if maintainers are open to it. My preference for the first slice:

1. Add `session_stop` as the main-session-only event.
2. Fire it after the main agent has produced the final assistant message for a turn, before the session settles.
3. Do not fire it for task/subagent sessions.
4. Keep `session_shutdown` teardown-only.
5. Support continuation feedback with an OMP-native result shape, e.g.:

```ts
return {
  continue: true,
  additionalContext: "Mission is not complete; continue with this next action."
};
```

6. Also accept the Claude/Codex-compatible shape if reasonable:

```json
{
  "decision": "block",
  "reason": "Mission is not complete; continue with this next action."
}
```

7. Include loop protection in the core contract:
   - `stop_hook_active` in the event payload;
   - default cap of 8 consecutive continuations, matching Claude/Codex unless there is a better OMP default.

8. Include at least these payload fields:
   - `turn_id`
   - `last_assistant_message`
   - session id
   - session file / transcript path when available
   - `stop_hook_active`

A later PR can cover `subagent_stop`, `user_prompt_submit`, and `post_compact` parity. For this issue, the main blocker is `session_stop` because mission/proof guards need main-agent Stop semantics now.

---

### @roboomp · 2026-06-17T05:31:47Z

The parity table matches the current OMP shape.

Verified current contracts:

- `input` is a text/image transform or `handled` short-circuit, not a developer-context hook with block/additionalContext semantics (`packages/coding-agent/src/extensibility/extensions/runner.ts:792-815`).
- `tool_call` can only return `{ block, reason }`; no input rewrite or model-visible additional context contract (`packages/coding-agent/src/extensibility/shared-events.ts:261-270`).
- `tool_result` can patch `{ content, details, isError }`; no continuation/block result (`packages/coding-agent/src/extensibility/shared-events.ts:272-283`, `packages/coding-agent/src/extensibility/extensions/runner.ts:636-676`).
- `tool_approval_requested` / `tool_approval_resolved` are event payloads; I do not see a handler result contract in the extension event path (`packages/coding-agent/src/extensibility/extensions/types.ts:640-656`).
- `session_compact` is notification-only; returned values are ignored by the generic `emit` path, while only `session_before_*` / `session.compacting` results are collected (`packages/coding-agent/src/extensibility/shared-events.ts:83-89`, `packages/coding-agent/src/extensibility/extensions/runner.ts:589-634`).

So yes: the gap is “awaited at the right lifecycle point and wired into the agent loop,” not aliases.

I would split implementation into two layers if maintainers accept this:

1. Core lifecycle contracts: `session_stop`, `subagent_start`, `subagent_stop`, `user_prompt_submit`, `permission_request`, and post-compact result handling.
2. Compatibility result normalization: accept OMP-native result fields and optionally map Claude/Codex-style `decision`, `reason`, `additionalContext`, `updatedInput`, etc. into those core contracts.

Open design decisions before code:

- whether OMP wants exact Claude/Codex field names or a native schema with adapters;
- which events may start a continuation turn vs only mutate the current operation;
- which tool inputs are safely rewritable and how validation failures surface;
- whether subagent continuation is supported or only observable;
- whether the stop-loop cap is global, per-session, or per-hook.

I’m still leaving code untouched because this is an unaccepted hook-compatibility proposal, not an authorized fix. If maintainers approve it, the smallest useful first PR is probably `session_stop` only, with loop protection and continuation result handling, then the remaining daily-hook parity as follow-ups.

---
If this didn't solve your issue, react 👎 on this comment and I'll keep it open.
Otherwise I'll auto-close in 4 hours.

---

### @roboomp · 2026-06-17T05:34:19Z

Acknowledged. I will not implement from reporter approval alone because this is an enhancement/proposal and there is no accepted maintainer signal yet.

If a maintainer comments “go ahead” / adds an accepted label, I’ll implement the first slice as scoped:

- `session_stop` main-session-only;
- awaited before settle after the final assistant message;
- continuation via `{ continue: true, additionalContext }` and compatible `{ decision: "block", reason }`;
- `stop_hook_active` plus an 8-continuation cap;
- payload with `turn_id`, `last_assistant_message`, session id, and session file/transcript path when available;
- no change to `session_shutdown` and no subagent stop behavior in this PR.

---
If this didn't solve your issue, react 👎 on this comment and I'll keep it open.
Otherwise I'll auto-close in 4 hours.

---