Skip to content

feat(hook): session-end Stop hook prompts to lock session corrections (#582) - #590

Merged
robotrocketscience merged 8 commits into
mainfrom
feat/issue-582-stop-hook-lock-prompt
May 10, 2026
Merged

feat(hook): session-end Stop hook prompts to lock session corrections (#582)#590
robotrocketscience merged 8 commits into
mainfrom
feat/issue-582-stop-hook-lock-prompt

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented May 10, 2026

Copy link
Copy Markdown
Owner

Closes #582.

What lands

A new Stop hook (aelf-stop-hook, default-on) that fires once per assistant-turn end, walks the store for unlocked correction-class beliefs created in the current session_id, and either emits a stderr listing with pre-filled aelf lock commands (default) or auto-locks them when AELF_AUTOLOCK_CORRECTIONS=1 is set in the environment.

Spec deviation — read this first

The issue's body assumed a aelf correct CLI command and a feedback_history.kind=correct marker that don't exist on main — neither was ever shipped. I confirmed: the only aelf subcommands related to belief mutation are lock, confirm, delete, promote, unlock. The feedback_history schema has no kind column.

So the issue's two proposed signals — (a) feedback_events with kind=correct and (b) beliefs modified via aelf correct CLI — are unimplementable as written.

This PR ships the implementable v0 (operator selected; recorded in #582 reply): query unlocked beliefs in the current session whose type=BELIEF_CORRECTION OR origin in {agent_inferred, agent_remembered}. That's the closest signal the schema actually exposes for "correction-class belief touched in this session." It does not capture beliefs whose existing content was modified — only newly-created ones from the session. A follow-up to ship aelf correct + feedback_history.kind would let this hook also surface modifications.

Atomic commits

7 SSH-signed commits, in dependency order:

  1. 0f5f2c0 feat(hook): stop() handler emits lock-prompt for session corrections (#582) — pure logic in hook.py: stop() + _belief_is_lock_candidate + _collect_lock_candidates + _format_stop_prompt + _autolock_enabled + _autolock_candidates + main_stop().
  2. 27f9507 build: register aelf-stop-hook console script (#582) — pyproject entry point.
  3. 39a8ec5 feat(setup): install/uninstall_stop_hook helpers (#582) — settings.json wiring under hooks.Stop, mirrors install_session_start_hook exactly.
  4. 4fbfcf6 feat(cli): wire stop hook into 'aelf setup' / 'aelf unsetup' (#582) — default-on installation, --no-stop-hook opt-out flag on both subcommands.
  5. d61be0a feat(doctor): nag when default-on Stop hook is missing (#582) — extends the v2.1 Claude not using aelf to remember checkpoints, only knows to use it to remember user preferences. #557 auto-capture nag to the fourth basename. Updates the doctor parity guardrail test.
  6. 098faeb test(hook): coverage for stop hook + setup install + doctor nag (#582) — 41 new tests across two files; updates 2 existing tests for the new fourth basename.
  7. 302f7a0 docs(changelog): unreleased entry for #582 stop hook + lock prompt.
  8. d7ca88d style(hook): use 'harness' phrasing in #582 docstrings (#582) — tiny phrasing fix to clear the pre-push discretion grep.

Acceptance

  • Stop hook installed and registered in settings.json via aelf setup (default-on, idempotent).
  • Hook fires at session end, queries store for this session's correction candidates.
  • If N > 0, emits stderr block with pre-filled aelf lock --statement '...' commands per candidate.
  • Block is suppressed when N == 0.
  • AELF_AUTOLOCK_CORRECTIONS=1 env var bypasses prompt, auto-locks candidates, logs each auto-lock to stderr.
  • aelf doctor checks for the hook's installation (extends the existing Claude not using aelf to remember checkpoints, only knows to use it to remember user preferences. #557 nag).
  • Coexists with the transcript-ingest Stop entry under the same hooks.Stop event key — both are separate entries; basename-uninstall of one does not displace the other (test test_uninstall_stop_hook_basename_does_not_remove_transcript_ingest).

Verification

  • uv run pytest -x -q3220 passed, 53 skipped. (3179 baseline + 41 new + 2 updated existing.)
  • Discretion grep on diff vs main: clean (one remaining "Claude Code" mention in style(hook) rephrasing was the only addition; that was the discretion-trip I rephrased away).
  • Pre-push hook clean.
  • All 8 commits SSH-signed.

Out of scope (deferred — explicit)

  • Capturing belief modifications, not only newly-created beliefs. Requires aelf correct CLI + feedback_history.kind column, neither of which exists on main. Should be filed as a follow-up issue if/when that infrastructure ships.
  • Performance: candidate-walk is O(n_beliefs) via list_belief_ids() + get_belief(). For typical session-end stores (<1k beliefs) this is sub-100ms. A focused SQL SELECT * FROM beliefs WHERE session_id=? AND lock_level != ? AND (type=? OR origin IN (...)) is a future optimisation when stores grow.
  • Telemetry: this hook does not currently log to the audit log alongside the UserPromptSubmit / SessionStart hooks. The information is in stderr only.

Summary by Sourcery

Add a default-on Stop hook that surfaces session-end correction-class beliefs for locking and wire it into setup, doctor, and CLI tooling.

New Features:

  • Introduce a session-end Stop hook that scans the current session for unlocked correction-class beliefs and either prompts users with pre-filled lock commands or auto-locks them based on an environment flag.

Enhancements:

  • Wire the new Stop hook into the setup/unsetup workflow, settings.json hook configuration, and doctor auto-capture checks so it is installed, removable, and monitored alongside existing hooks.

Documentation:

  • Document the new session-end Stop hook behavior, configuration, and defaults in the changelog.

Tests:

  • Add focused tests covering Stop hook filtering, prompting and autolock behavior, plus setup/install/uninstall wiring and doctor parity for the new hook.

…582)

Adds a Stop hook entry point that enumerates correction-class beliefs
created in the current session and either emits a stderr listing with
pre-filled aelf lock commands, or auto-locks them when
AELF_AUTOLOCK_CORRECTIONS=1 is set.

Candidate filter:
  session_id == current
  AND lock_level != LOCK_USER
  AND (type == BELIEF_CORRECTION OR origin in {agent_inferred,
       agent_remembered}).

The candidate-walk is one list_belief_ids() + one get_belief() per id;
small enough for typical session-end stores. A focused SQL query is a
follow-up when stores grow.

Hook contract: never blocks, never raises. Empty payload, missing
session_id, store errors all return 0 silently.

main_stop() entry point added. pyproject.toml wiring + setup.py
install/uninstall + aelf doctor checks land in follow-up commits.
Mirrors the install_session_start_hook pattern: idempotent settings.json
mutation under hooks.Stop, basename + exact-match uninstall semantics,
atomic _atomic_write commit. Stop event coexists with the
transcript-ingest Stop entry as a separate entry under the same key.

resolve_stop_hook_command picks the absolute path with the same
project=venv-first / user=PATH-first precedence as the other hook
script resolvers.

CLI wiring of these helpers into 'aelf setup' / 'aelf uninstall' lands
in the next commit.
Default-on Stop hook installation, mirrors the SessionStart pattern.
--no-stop-hook flag opts out on either subcommand. Behaviour:

  aelf setup           # installs Stop hook (default ON)
  aelf setup --no-stop-hook
  aelf unsetup         # removes it (default ON)
  aelf unsetup --no-stop-hook

The Stop hook coexists with the existing transcript-ingest Stop entry
under the same hooks.Stop event key in settings.json.

Set AELF_AUTOLOCK_CORRECTIONS=1 in the env to make the hook auto-lock
session corrections instead of emitting the lock-prompt.
Extends the v2.1 auto-capture nag (#557) to flag installs missing
aelf-stop-hook from every scanned settings.json. Catches the
upgrade-from-pre-#582 case where the user's settings.json was
written by an aelf setup that ran before the Stop hook landed.

Updates the parity guardrail test in test_doctor.py to include
STOP_HOOK_SCRIPT_NAME in the expected set.
41 new tests across two files:
  - tests/test_hook_stop_lock_prompt.py — predicate + collection +
    formatter + autolock-env + autolock-mutates + integration
    (empty/malformed payload, missing session_id, no candidates,
    prompt-mode listing, AUTOLOCK env-mode locking).
  - tests/test_setup_stop_hook.py — install + uninstall idempotency,
    coexistence with the transcript-ingest Stop entry under the same
    Stop event key, basename-uninstall scoped to aelf-stop-hook only.

Also updates the existing doctor + cli-setup tests to expect the
fourth default-on basename in the auto-capture nag set, and to pass
--no-stop-hook in the opt-out integration scenario.

Full suite: 3220 passed, 53 skipped.
Rewrites three docstring lines added in 0f5f2c0 to use 'harness' /
'the Stop-hook contract' instead of naming the host platform. Matches
the surrounding existing code's phrasing and clears the pre-push
discretion grep on this branch.
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 14 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2d771882-341f-45f0-85c8-09149c2e424d

📥 Commits

Reviewing files that changed from the base of the PR and between 9e5da11 and 2f0aaec.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !**/CHANGELOG.md
📒 Files selected for processing (9)
  • pyproject.toml
  • src/aelfrice/cli.py
  • src/aelfrice/doctor.py
  • src/aelfrice/hook.py
  • src/aelfrice/setup.py
  • tests/test_cli_setup.py
  • tests/test_doctor.py
  • tests/test_hook_stop_lock_prompt.py
  • tests/test_setup_stop_hook.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-582-stop-hook-lock-prompt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai

sourcery-ai Bot commented May 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements a new default-on session-end Stop hook (aelf-stop-hook) that scans the current session’s store for unlocked correction-class beliefs and either prints a stderr lock-prompt block or auto-locks them, and wires this hook into setup/unsetup, doctor diagnostics, entry points, and tests/docs alongside the existing hooks.

Sequence diagram for the new session-end Stop hook lock prompt

sequenceDiagram
    actor User
    participant Harness
    participant StopHook as aelf_stop_hook
    participant Store as MemoryStore

    User->>Harness: Complete assistant turn
    Harness->>StopHook: Invoke with Stop JSON payload on stdin

    StopHook->>StopHook: _autolock_enabled(env)
    StopHook->>StopHook: _extract_session_id(raw)
    alt session_id missing or payload invalid
        StopHook-->>Harness: Return 0 (no output)
    else session_id present
        StopHook->>StopHook: _open_store()
        StopHook->>Store: list_belief_ids()
        loop For each belief id
            StopHook->>Store: get_belief(bid)
            StopHook->>StopHook: _belief_is_lock_candidate(b, session_id)
        end
        StopHook->>StopHook: _collect_lock_candidates(...)
        alt No candidates
            StopHook-->>Harness: Return 0 (no output)
        else Candidates found
            alt AELF_AUTOLOCK_CORRECTIONS truthy
                StopHook->>Store: update_belief() with LOCK_USER and origin user_stated
                StopHook->>Harness: stderr "aelfrice: auto-locked ..."
                StopHook-->>Harness: Return 0
            else Prompt mode
                StopHook->>StopHook: _format_stop_prompt(candidates)
                StopHook->>Harness: Write <aelfrice-session-end> block to stderr
                StopHook-->>Harness: Return 0
            end
        end
        Store-->>StopHook: close()
    end
Loading

File-Level Changes

Change Details Files
Add session-end Stop hook implementation that finds correction-class beliefs for the current session and prompts or auto-locks them.
  • Import correction-related constants and origins needed to identify candidate beliefs.
  • Implement _belief_is_lock_candidate and _collect_lock_candidates to filter unlocked, session-scoped correction-class beliefs using type and origin.
  • Add _format_stop_prompt and _shell_quote to render a tagged stderr block with per-belief aelf lock --statement commands.
  • Add _autolock_enabled, _autolock_candidates, and _utc_now_iso helpers to support environment-controlled auto-locking and state updates.
  • Implement stop() hook handler that reads the Stop payload, opens the store, collects candidates, then either auto-locks or prints the formatted block, always failing soft and returning 0.
  • Expose main_stop() entry point for the aelf-stop-hook console script.
src/aelfrice/hook.py
Wire the Stop hook into installation, CLI, entry points, and doctor auto-capture checks.
  • Register aelf-stop-hook console script pointing to aelfrice.hook:main_stop.
  • Introduce STOP event key and script name constants, plus resolve_stop_hook_command, install_stop_hook, and uninstall_stop_hook, mirroring SessionStart semantics and coexisting with transcript-ingest Stop entries.
  • Extend aelf setup to install the Stop hook by default with --[no-]stop-hook flag and matching status messages.
  • Extend aelf unsetup to uninstall Stop hook entries by default with a --[no-]stop-hook flag using basename-based removal.
  • Include Stop hook name in the doctor auto-capture required basenames list and update the missing-hooks guidance string to mention the stop hook and its opt-out flag.
  • Re-export STOP_HOOK_SCRIPT_NAME in cli imports and update tests ensuring doctor’s expected basenames stay in sync with setup.
pyproject.toml
src/aelfrice/setup.py
src/aelfrice/cli.py
src/aelfrice/doctor.py
tests/test_doctor.py
tests/test_cli_setup.py
Add focused tests for the Stop hook behavior and setup integration plus documentation entry.
  • Create test_hook_stop_lock_prompt.py to cover candidate selection, collection, prompt formatting, env parsing, autolock mutation, and stop() end-to-end behaviors including malformed payloads and AUTOLOCK env var.
  • Create test_setup_stop_hook.py mirroring session-start tests to verify Stop hook install/uninstall idempotency, coexistence with transcript-ingest Stop entries, and basename-only uninstallation behavior.
  • Update CLI setup tests’ default --no-* argument list to include --no-stop-hook.
  • Add CHANGELOG entry documenting the new aelf-stop-hook, its candidate filter, AUTOLOCK behavior, setup/unsetup wiring, and doctor integration, including the spec deviation explanation from feat(hook): session-end Stop hook prompts to aelf:lock corrections from this session #582.
  • Tweak a hook docstring phrasing to use "harness" terminology for consistency with discretion checks.
tests/test_hook_stop_lock_prompt.py
tests/test_setup_stop_hook.py
tests/test_cli_setup.py
CHANGELOG.md
src/aelfrice/hook.py

Assessment against linked issues

Issue Objective Addressed Explanation
#582 Add a Stop hook that runs at session end, identifies this session's unlocked correction-class beliefs, and when any exist emits a stderr block (suppressed when none) listing each candidate with pre-filled lock commands.
#582 Support an AELF_AUTOLOCK_CORRECTIONS=1 environment variable that bypasses the prompt, auto-locks the identified correction candidates, and logs each auto-lock to stderr.
#582 Wire the Stop hook into setup and diagnostics so that aelf setup installs it by default (with an opt-out flag) and aelf doctor checks and reports on its installation like other default-on hooks.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@robotrocketscience robotrocketscience added author-noether Authored by parallel session noether attn:review Needs review (PR open, awaiting reviewer) labels May 10, 2026

@sourcery-ai sourcery-ai 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.

Hey - I've found 2 issues, and left some high level feedback:

  • In stop(), you parse the JSON payload into payload but then call _extract_session_id(raw) instead of reading from the parsed dict; consider using payload.get("session_id") to avoid double work and reduce reliance on the string-level extractor.
  • The broad except Exception inside _autolock_candidates will silently swallow programming errors (e.g., schema mismatches) along with expected store failures; consider catching narrower exceptions or at least logging a full traceback to aid debugging while still honoring the fail-soft contract.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `stop()`, you parse the JSON payload into `payload` but then call `_extract_session_id(raw)` instead of reading from the parsed dict; consider using `payload.get("session_id")` to avoid double work and reduce reliance on the string-level extractor.
- The broad `except Exception` inside `_autolock_candidates` will silently swallow programming errors (e.g., schema mismatches) along with expected store failures; consider catching narrower exceptions or at least logging a full traceback to aid debugging while still honoring the fail-soft contract.

## Individual Comments

### Comment 1
<location path="src/aelfrice/hook.py" line_range="1582-1591" />
<code_context>
+    serr = stderr if stderr is not None else sys.stderr
+    if not _IMPORTS_OK:
+        return 0
+    try:
+        raw = sin.read()
+        if not raw or not raw.strip():
+            return 0
+        try:
+            payload = json.loads(raw)
+        except json.JSONDecodeError:
+            return 0
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid parsing the Stop payload JSON if the parsed object is never used

`payload = json.loads(raw)` is never used; the rest of the function works directly with `raw` and `_extract_session_id(raw)`. This adds unnecessary overhead on a hot path and could cause behavior drift if `_extract_session_id` later expects different input. Either remove the parse or change the code to use `payload` (for example, by having `_extract_session_id` take the parsed dict).
</issue_to_address>

### Comment 2
<location path="tests/test_setup_stop_hook.py" line_range="107-104" />
<code_context>
+    assert result.removed == 0
+
+
+def test_uninstall_stop_hook_missing_file(tmp_path: Path) -> None:
+    p = tmp_path / "nope.json"
+    result = uninstall_stop_hook(p, command=_STOP_CMD)
+    assert result.removed == 0
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Mirror the validation tests from session-start hooks for `uninstall_stop_hook` argument error cases.

These tests only exercise the happy path. Please also add cases covering `uninstall_stop_hook`’s argument validation (both `command` and `command_basename` missing, both provided, and empty-string inputs), mirroring the existing `uninstall_session_start_hook` validation tests so behavior stays consistent and guarded against regressions.

Suggested implementation:

```python
import json
from pathlib import Path

import pytest

```

```python
from aelfrice.setup import (
    install_stop_hook,
    uninstall_stop_hook,
    install_transcript_ingest_hooks,

```

```python
def test_uninstall_stop_hook_no_match(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    install_stop_hook(p, command=_STOP_CMD)
    result = uninstall_stop_hook(p, command="/different/binary")
    assert result.removed == 0


def test_uninstall_stop_hook_missing_file(tmp_path: Path) -> None:
    p = tmp_path / "nope.json"
    result = uninstall_stop_hook(p, command=_STOP_CMD)
    assert result.removed == 0


def test_uninstall_stop_hook_requires_command_or_basename(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(p)


def test_uninstall_stop_hook_rejects_both_command_and_basename(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(
            p,
            command=_STOP_CMD,
            command_basename="stop-binary",
        )


def test_uninstall_stop_hook_rejects_empty_command(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(p, command="")


def test_uninstall_stop_hook_rejects_empty_command_basename(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(p, command_basename="")

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/aelfrice/hook.py
Comment on lines +1582 to +1591
try:
b.lock_level = LOCK_USER
b.locked_at = now
b.demotion_pressure = 0
b.origin = ORIGIN_USER_STATED
store.update_belief(b)
locked += 1
print(
f"aelfrice: auto-locked {b.id} ({b.type}, origin→user_stated)",
file=stderr,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (performance): Avoid parsing the Stop payload JSON if the parsed object is never used

payload = json.loads(raw) is never used; the rest of the function works directly with raw and _extract_session_id(raw). This adds unnecessary overhead on a hot path and could cause behavior drift if _extract_session_id later expects different input. Either remove the parse or change the code to use payload (for example, by having _extract_session_id take the parsed dict).

p = tmp_path / "settings.json"
install_stop_hook(p, command=_STOP_CMD)
result = uninstall_stop_hook(p, command="/different/binary")
assert result.removed == 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Mirror the validation tests from session-start hooks for uninstall_stop_hook argument error cases.

These tests only exercise the happy path. Please also add cases covering uninstall_stop_hook’s argument validation (both command and command_basename missing, both provided, and empty-string inputs), mirroring the existing uninstall_session_start_hook validation tests so behavior stays consistent and guarded against regressions.

Suggested implementation:

import json
from pathlib import Path

import pytest
from aelfrice.setup import (
    install_stop_hook,
    uninstall_stop_hook,
    install_transcript_ingest_hooks,
def test_uninstall_stop_hook_no_match(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    install_stop_hook(p, command=_STOP_CMD)
    result = uninstall_stop_hook(p, command="/different/binary")
    assert result.removed == 0


def test_uninstall_stop_hook_missing_file(tmp_path: Path) -> None:
    p = tmp_path / "nope.json"
    result = uninstall_stop_hook(p, command=_STOP_CMD)
    assert result.removed == 0


def test_uninstall_stop_hook_requires_command_or_basename(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(p)


def test_uninstall_stop_hook_rejects_both_command_and_basename(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(
            p,
            command=_STOP_CMD,
            command_basename="stop-binary",
        )


def test_uninstall_stop_hook_rejects_empty_command(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(p, command="")


def test_uninstall_stop_hook_rejects_empty_command_basename(tmp_path: Path) -> None:
    p = tmp_path / "settings.json"
    with pytest.raises(ValueError):
        uninstall_stop_hook(p, command_basename="")

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Maxwell:2026-05-10T08:11:31Z]

@robotrocketscience
robotrocketscience merged commit 2f0aaec into main May 10, 2026
24 of 31 checks passed
@robotrocketscience
robotrocketscience deleted the feat/issue-582-stop-hook-lock-prompt branch May 10, 2026 08:12
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Maxwell:2026-05-10T08:12:44Z]

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

Labels

attn:review Needs review (PR open, awaiting reviewer) author-noether Authored by parallel session noether

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(hook): session-end Stop hook prompts to aelf:lock corrections from this session

1 participant