From 0c8e76226d59eabbf4c1511a8fb49e603f4484dc Mon Sep 17 00:00:00 2001 From: "Charles C. Figueiredo" Date: Sun, 12 Jul 2026 17:02:18 -0400 Subject: [PATCH 1/2] fix Codex hook command dispatch --- CHANGELOG.md | 5 ++++ plugin/.codex-plugin/plugin.json | 2 +- plugin/hooks/hooks.codex.json | 8 +++--- plugin/tests/test_plugin.py | 48 ++++++++++++++++++++++++++++++++ tests/test_plugin_assets.py | 8 +++--- 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcd5349..cf6e0e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning: [S ## [Unreleased] +### Fixed +- Codex plugin 0.1.2 encodes SessionStart/SessionEnd hooks as the documented + single command string. Codex previously ignored the unsupported `args` array, + ran bare `sh`, and fed hook JSON to it as shell input, causing exit code 127. + ## [0.24.1] - 2026-07-12 ### Fixed diff --git a/plugin/.codex-plugin/plugin.json b/plugin/.codex-plugin/plugin.json index 45c6ff6..6a0ee8b 100644 --- a/plugin/.codex-plugin/plugin.json +++ b/plugin/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agentcairn", - "version": "0.1.1", + "version": "0.1.2", "description": "Local-first agent memory for Codex — recall, remember, and ambient capture into a Markdown vault you own.", "author": { "name": "Charles C. Figueiredo", "email": "ccf@ccf.io" }, "homepage": "https://agentcairn.dev", diff --git a/plugin/hooks/hooks.codex.json b/plugin/hooks/hooks.codex.json index b57582f..57e7cde 100644 --- a/plugin/hooks/hooks.codex.json +++ b/plugin/hooks/hooks.codex.json @@ -1,12 +1,12 @@ { "hooks": { "SessionStart": [ - { "hooks": [ { "type": "command", "command": "sh", - "args": ["${PLUGIN_ROOT}/scripts/session-start.sh"], "timeout": 20 } ] } + { "hooks": [ { "type": "command", + "command": "/bin/sh \"${PLUGIN_ROOT}/scripts/session-start.sh\"", "timeout": 20 } ] } ], "SessionEnd": [ - { "hooks": [ { "type": "command", "command": "sh", - "args": ["${PLUGIN_ROOT}/scripts/session-end.sh"], "timeout": 120 } ] } + { "hooks": [ { "type": "command", + "command": "/bin/sh \"${PLUGIN_ROOT}/scripts/session-end.sh\"", "timeout": 120 } ] } ] } } diff --git a/plugin/tests/test_plugin.py b/plugin/tests/test_plugin.py index fc17f70..5acf1d0 100644 --- a/plugin/tests/test_plugin.py +++ b/plugin/tests/test_plugin.py @@ -58,6 +58,54 @@ def test_mcp_config_wires_uvx_agentcairn(): assert srv["env"]["CAIRN_VAULT"] == "${user_config.vault_path}" +def test_codex_hooks_use_documented_single_command_strings(): + hooks = _json(PLUGIN / "hooks" / "hooks.codex.json")["hooks"] + expected = { + "SessionStart": '/bin/sh "${PLUGIN_ROOT}/scripts/session-start.sh"', + "SessionEnd": '/bin/sh "${PLUGIN_ROOT}/scripts/session-end.sh"', + } + for event, command in expected.items(): + handler = hooks[event][0]["hooks"][0] + assert handler["command"] == command + assert "args" not in handler + + +def test_codex_session_start_handler_executes_script_instead_of_stdin(tmp_path): + hooks = _json(PLUGIN / "hooks" / "hooks.codex.json")["hooks"] + handler = hooks["SessionStart"][0]["hooks"][0] + scripts = tmp_path / "scripts" + scripts.mkdir() + stub = scripts / "session-start.sh" + stub.write_text( + "#!/bin/sh\n" + 'grep -q \'"hook_event_name": "SessionStart"\' || exit 2\n' + "printf '%s\\n' " + '\'{"hookSpecificOutput":{"hookEventName":"SessionStart",' + '"additionalContext":"Codex hook works"}}\'\n' + ) + stub.chmod(0o755) + env = { + **os.environ, + "PLUGIN_ROOT": str(tmp_path), + } + + result = subprocess.run( + handler["command"], + shell=True, + executable="/bin/sh", + input=json.dumps({"hook_event_name": "SessionStart", "cwd": str(ROOT)}), + capture_output=True, + text=True, + env=env, + timeout=10, + ) + + assert result.returncode == 0, result.stderr + output = json.loads(result.stdout) + assert output["hookSpecificOutput"]["hookEventName"] == "SessionStart" + assert "Codex hook works" in output["hookSpecificOutput"]["additionalContext"] + + def _run_hook(script, stdin_obj, env_extra, cwd=None, first_run=False): env = {**os.environ, **env_extra} # The index is now vault-derived, so session-start.sh detects a first run via the diff --git a/tests/test_plugin_assets.py b/tests/test_plugin_assets.py index e8bb7f4..95b659c 100644 --- a/tests/test_plugin_assets.py +++ b/tests/test_plugin_assets.py @@ -33,10 +33,10 @@ def test_codex_mcp_is_bare_map_and_defers_to_shared_config(): def test_codex_hooks_reference_existing_scripts(): h = _load(PLUGIN / "hooks" / "hooks.codex.json") - starts = h["hooks"]["SessionStart"][0]["hooks"][0]["args"] - ends = h["hooks"]["SessionEnd"][0]["hooks"][0]["args"] - assert starts == ["${PLUGIN_ROOT}/scripts/session-start.sh"] - assert ends == ["${PLUGIN_ROOT}/scripts/session-end.sh"] + starts = h["hooks"]["SessionStart"][0]["hooks"][0]["command"] + ends = h["hooks"]["SessionEnd"][0]["hooks"][0]["command"] + assert starts == '/bin/sh "${PLUGIN_ROOT}/scripts/session-start.sh"' + assert ends == '/bin/sh "${PLUGIN_ROOT}/scripts/session-end.sh"' assert (PLUGIN / "scripts" / "session-start.sh").is_file() assert (PLUGIN / "scripts" / "session-end.sh").is_file() From b8e0eddcef018c83b7d545d88c8672df8dfa4b4a Mon Sep 17 00:00:00 2001 From: "Charles C. Figueiredo" Date: Sun, 12 Jul 2026 18:51:52 -0400 Subject: [PATCH 2/2] release agentcairn 0.24.2 --- CHANGELOG.md | 5 ++++- src/cairn/__init__.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf6e0e3..2d119f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning: [S ## [Unreleased] +## [0.24.2] - 2026-07-12 + ### Fixed - Codex plugin 0.1.2 encodes SessionStart/SessionEnd hooks as the documented single command string. Codex previously ignored the unsupported `args` array, @@ -363,7 +365,8 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning: [S - Out-of-band capture from coding-agent transcripts (redacted, non-lossy `remember`). - Published to PyPI via GitHub Trusted Publishing (OIDC, no stored secrets). -[Unreleased]: https://github.com/ccf/agentcairn/compare/v0.24.1...HEAD +[Unreleased]: https://github.com/ccf/agentcairn/compare/v0.24.2...HEAD +[0.24.2]: https://github.com/ccf/agentcairn/compare/v0.24.1...v0.24.2 [0.24.1]: https://github.com/ccf/agentcairn/compare/v0.24.0...v0.24.1 [0.24.0]: https://github.com/ccf/agentcairn/compare/v0.23.0...v0.24.0 [0.23.0]: https://github.com/ccf/agentcairn/compare/v0.22.1...v0.23.0 diff --git a/src/cairn/__init__.py b/src/cairn/__init__.py index a3d4cfc..ed95c46 100644 --- a/src/cairn/__init__.py +++ b/src/cairn/__init__.py @@ -1,4 +1,4 @@ # SPDX-License-Identifier: Apache-2.0 """agentcairn — local-first agent memory (import package `cairn`).""" -__version__ = "0.24.1" +__version__ = "0.24.2"