Skip to content

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

@zew1me

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions