Skip to content

fix(codex): refresh encoded hook commands - #90

Open
xuhaonan013 wants to merge 2 commits into
evo-hq:mainfrom
xuhaonan013:fix/codex-encoded-hooks-84
Open

xuhaonan013 wants to merge 2 commits into
evo-hq:mainfrom
xuhaonan013:fix/codex-encoded-hooks-84

Conversation

@xuhaonan013

@xuhaonan013 xuhaonan013 commented Aug 22, 2026

Copy link
Copy Markdown

Summary

Teach the Codex hook materializer to recognize Evo drain commands inside the existing base64-encoded Node wrapper, so repeat installs can refresh stale commands.

Fixes #84

What changed

  • Preserve support for the historical ${CLAUDE_PLUGIN_ROOT}/bin/evo-hook-drain source command.
  • Recognize only the exact node -e "eval(Buffer.from(...,'base64').toString())" wrapper emitted by Evo, validate and decode its payload, then look for the drain marker in the decoded script.
  • Fail closed for malformed base64, non-UTF-8 payloads, different wrapper shapes, and unrelated Node commands.
  • Add regressions for encoded-command refresh, current-command idempotency, plaintext compatibility, and malformed/unrelated wrappers.

Why

After a hook has already been materialized, the literal evo-hook-drain marker exists only in the decoded payload. Matching the outer command therefore treated repeat installs as a no-op and prevented future wrapper updates (such as timeout changes) from propagating without --force.

Test plan

  • pytest tests/unit/test_codex_install_migration.py -q - 22 passed, 5 subtests passed
  • python tests/unit/test_codex_install_migration.py -v on Python 3.10 and 3.14 - 22 passed on each
  • pytest tests/unit/ -q in a Linux-native CI-equivalent copy - 803 passed, 48 skipped, 5 subtests passed
  • ruff check ... --select E4,E7,E9,F63,F7,F82
  • python -m compileall -q on both changed files

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +66 to +81
def _is_evo_hook_drain_command(command: str) -> bool:
"""Recognize source and already-materialized evo drain commands."""
if "${CLAUDE_PLUGIN_ROOT}/bin/evo-hook-drain" in command:
return True

prefix = "node -e \"eval(Buffer.from('"
suffix = "','base64').toString())\""
if not command.startswith(prefix) or not command.endswith(suffix):
return False

encoded = command[len(prefix):-len(suffix)]
try:
script = base64.b64decode(encoded, validate=True).decode("utf-8")
except (binascii.Error, UnicodeDecodeError):
return False
return "evo-hook-drain" in script

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Legacy plain-path hook command never refreshed

When a Codex hooks.json still carries the v0.6.1 form — a bare absolute path to evo-hook-drain_is_evo_hook_drain_command returns false, so reinstall leaves it unchanged where the old "evo-hook-drain" in cmd check rewrote it. That command lacks the timeout guard added later, so those hooks can hang indefinitely and no reinstall repairs them.

Prompt for agents
The new _is_evo_hook_drain_command in plugins/evo/src/evo/host_install/codex.py is strictly narrower than the old substring check "evo-hook-drain" in cmd that it replaces at the call site (line 110). Version 0.6.1 (commit 33d87a4) materialized Codex hooks.json commands as a bare shell-quoted absolute path to the stable binary (e.g. /home/user/.evo/bin/evo-hook-drain via _command_quote(stable_binary_path())). The old code recognized this and rewrote it to the current node-wrapper command; the new function does not, so upgrading users stuck on that form keep a hook command lacking the timeout/killSignal guard added in c1e6c2c, which can hang Codex sessions. Consider also recognizing a bare command whose token resolves to a path ending in evo-hook-drain (i.e. matching the stable binary path or a path ending in /bin/evo-hook-drain) in addition to the CLAUDE_PLUGIN_ROOT source form and the base64 node wrapper, so legacy plain-path materializations are still refreshed.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 946b14d. Legacy v0.6.1 commands are now parsed as shell tokens and accepted only when there is exactly one token whose path is absolute, whose parent directory is �in, and whose filename is �vo-hook-drain or �vo-hook-drain.exe. The ${CLAUDE_PLUGIN_ROOT} source form remains an exact match. Added a cross-platform regression built with the historical shlex.quote / subprocess.list2cmdline behavior, plus negative cases for relative and argument-bearing commands. Focused tests: 22 passed on Python 3.10, 3.12, and 3.14. Full unit suite: 803 passed, 48 skipped, 5 subtests passed.

@xuhaonan013

Copy link
Copy Markdown
Author

The PR-focused Codex migration test file passes locally: 22 tests plus 5 subtests. The plugin sdist/wheel build and the 10-source version-consistency check also pass.

The repository-wide session fixture additionally requires Cargo; GitHub CI installs Rust, but the current workflow run is still action_required. Could a maintainer approve the CI run and review this PR when convenient?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_materialize_codex_hooks matches on raw command string, misses already-encoded hooks

1 participant