Skip to content

Codex Stop hook: serena-hooks cleanup --client=codex emits invalid Stop-hook stdout #1533

Description

@zew1me

Summary

When serena-hooks cleanup --client=codex is configured as a Codex Stop hook, Codex can report:

Stop hook (failed)
error: hook returned invalid stop hook JSON output

I hit this in a real Codex session with this hook config:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "command": "serena-hooks cleanup --client=codex",
            "statusMessage": "Cleaning up Serena",
            "type": "command"
          }
        ]
      }
    ]
  }
}

This may be adjacent to #1480, but the concrete failure here is Codex-specific: the Stop hook command's stdout must be valid Stop-hook JSON.

Environment

  • Serena command: serena-hooks cleanup --client=codex
  • Serena install path in this setup: uv tool managed serena-agent
  • Python runtime shown by traceback: Python 3.13 tool env
  • Client: Codex
  • OS: macOS

Scenario 1: realistic Codex Stop input, empty stdout

With a session_id present, cleanup exits successfully but emits no stdout:

printf '%s' '{"session_id":"test-session","stop_hook_active":false}' \
  | serena-hooks cleanup --client=codex

Observed:

# exit code: 0
# stdout: empty

Codex expects Stop hook stdout to be a valid JSON object. Empty stdout is therefore parsed as invalid Stop-hook JSON.

For comparison, another Codex Stop hook in the same config emits:

{"continue": true}

and does not trigger the JSON error.

Scenario 2: missing session_id, Python traceback on stdout/stderr

If cleanup receives input without session_id, it raises a Python traceback:

printf '%s' '{"stop_hook_active":false}' \
  | serena-hooks cleanup --client=codex

Observed traceback:

Traceback (most recent call last):
  File "/Users/nigelstuke/.local/bin/serena-hooks", line 10, in <module>
    sys.exit(hook_commands())
             ~~~~~~~~~~~~~^^
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/click/core.py", line 1524, in __call__
    return self.main(*args, **kwargs)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/click/core.py", line 1445, in main
    rv = self.invoke(ctx)
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/click/core.py", line 1912, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/click/core.py", line 1308, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/click/core.py", line 877, in invoke
    return callback(*args, **kwargs)
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/serena/hooks.py", line 589, in cleanup
    SessionEndCleanupHook(HookClient(client)).execute()
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/Users/nigelstuke/.local/share/uv/tools/serena-agent/lib/python3.13/site-packages/serena/hooks.py", line 38, in __init__
    raise ValueError("Session ID is required in the hook input data")
ValueError: Session ID is required in the hook input data

A traceback is also not valid Stop-hook JSON, so Codex reports the same class of failure.

What Codex expects

For a Stop hook, Codex expects stdout to contain a valid JSON object. The minimal successful response shape is:

{"continue": true}

Other logs or diagnostics should go to stderr or to a file, and the hook should avoid emitting non-JSON stdout.

Expected behavior

For --client=codex, serena-hooks cleanup should always produce valid Codex Stop-hook JSON on stdout, including when cleanup is a no-op or when the input is missing optional/expected fields.

For example:

{"continue": true}

If cleanup fails internally, it could still either:

  • emit valid JSON and log details elsewhere, or
  • emit a Codex-compatible blocking JSON response if there is a reason Codex should not continue.

Actual behavior

  • With a session_id, stdout is empty.
  • Without a session_id, the command raises a Python traceback.
  • Codex reports hook returned invalid stop hook JSON output.

Local workaround

Wrapping the command to suppress Serena's own output and explicitly emit Codex-compatible JSON avoids the error:

serena-hooks cleanup --client=codex >/dev/null 2>&1; printf '{"continue":true}\n'

This is only a workaround; it would be nicer if the --client=codex hook command handled Codex's Stop-hook stdout contract directly.

Related

  • Claude Code hooks #1480 discusses whether cleanup belongs on Claude Code Stop vs SessionEnd. This issue is narrower: when cleanup is used as a Codex Stop hook, stdout needs to be JSON-compatible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions