Skip to content

fix(hooks): log warning when poststop hooks fail#2045

Merged
kolyshkin merged 1 commit intocontainers:mainfrom
jnovy:fix/issue-1946
Mar 16, 2026
Merged

fix(hooks): log warning when poststop hooks fail#2045
kolyshkin merged 1 commit intocontainers:mainfrom
jnovy:fix/issue-1946

Conversation

@jnovy
Copy link
Contributor

@jnovy jnovy commented Mar 12, 2026

Poststop hook errors were silently ignored when the hook returned a positive exit code. The error check only caught negative return values, missing the case where hooks fail with a non-zero exit status.

Change the condition from ret < 0 to ret != 0 and create a proper error message for positive exit codes so crun_error_write_warning_and_release has something to print. Note that do_hooks() also logs via libcrun_warning() but that is suppressed at default verbosity, so the explicit crun_error_write_warning_and_release call ensures the warning is always visible.

Fixes: #1946

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where poststop hook failures were not properly reported, leading to silent errors. By refining the error detection logic and ensuring explicit warning messages are generated, it significantly improves the observability and debugging capabilities for container lifecycle events. New tests validate this improved error handling.

Highlights

  • Poststop Hook Error Handling: Changed the error check for poststop hooks from ret < 0 to ret != 0 to correctly catch non-zero exit codes, ensuring that failures are no longer silently ignored.
  • Explicit Warning Logging: Introduced explicit warning messages for failing poststop hooks, making them visible even at default verbosity levels, unlike the previously suppressed libcrun_warning().
  • New Test Cases: Added new Python test cases to verify that failing poststop hooks correctly log warnings and that the container cleanup process is not prevented by multiple hook failures.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/libcrun/container.c
    • Updated the condition for detecting do_hooks failure from ret < 0 to ret != 0.
    • Added logic to create a specific error message for positive return codes from do_hooks before logging the warning.
  • tests/test_hooks.py
    • Added test_poststop_hook_failure_warning to verify that a single failing poststop hook logs a warning.
    • Added test_multiple_poststop_hooks_failure to ensure that multiple failing poststop hooks still result in warnings and do not prevent container cleanup.
    • Included the new test functions in the main test dictionary.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly addresses an issue where poststop hook failures with positive exit codes were silently ignored. The change to check for ret != 0 instead of ret < 0 in src/libcrun/container.c is the right fix. The addition of new tests in tests/test_hooks.py effectively validates this new behavior. I've suggested a small refactoring in the test file to reduce code duplication and improve maintainability. Overall, this is a good fix.

Comment on lines +417 to +459
def test_poststop_hook_failure_warning():
"""Test that a failing poststop hook logs a warning but container still succeeds."""
conf = base_config()
add_all_namespaces(conf)
conf['process']['args'] = ['/init', 'true']

hook = {"path": "/bin/false"}
conf['hooks'] = {"poststop": [hook]}

try:
out, _ = run_and_get_output(conf)
except Exception as e:
logger.error("container failed unexpectedly: %s", e)
return -1

if "poststop hook failed with exit code" not in out:
logger.error("expected warning about poststop hook failure, got: %s", out)
return -1

return 0


def test_multiple_poststop_hooks_failure():
"""Test that multiple failing poststop hooks do not prevent container cleanup."""
conf = base_config()
add_all_namespaces(conf)
conf['process']['args'] = ['/init', 'true']

hook1 = {"path": "/bin/false"}
hook2 = {"path": "/bin/false"}
conf['hooks'] = {"poststop": [hook1, hook2]}

try:
out, _ = run_and_get_output(conf)
except Exception as e:
logger.error("container failed unexpectedly: %s", e)
return -1

if "poststop hook failed with exit code" not in out:
logger.error("expected warning about poststop hook failure, got: %s", out)
return -1

return 0

Choose a reason for hiding this comment

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

medium

These two test functions, test_poststop_hook_failure_warning and test_multiple_poststop_hooks_failure, are nearly identical, leading to code duplication. To improve maintainability, you can extract the common logic into a helper function. This will make the tests cleaner and easier to manage in the future.

def _test_failing_poststop_hooks(hooks):
    """Helper for testing that failing poststop hooks log a warning but the container succeeds."""
    conf = base_config()
    add_all_namespaces(conf)
    conf['process']['args'] = ['/init', 'true']
    conf['hooks'] = {"poststop": hooks}

    try:
        out, _ = run_and_get_output(conf)
    except Exception as e:
        logger.error("container failed unexpectedly: %s", e)
        return -1

    if "poststop hook failed with exit code" not in out:
        logger.error("expected warning about poststop hook failure, got: %s", out)
        return -1

    return 0


def test_poststop_hook_failure_warning():
    """Test that a failing poststop hook logs a warning but container still succeeds."""
    return _test_failing_poststop_hooks([{"path": "/bin/false"}])


def test_multiple_poststop_hooks_failure():
    """Test that multiple failing poststop hooks do not prevent container cleanup."""
    return _test_failing_poststop_hooks([{"path": "/bin/false"}, {"path": "/bin/false"}])

@packit-as-a-service
Copy link

TMT tests failed. @containers/packit-build please check.

@kolyshkin
Copy link
Collaborator

@eriksjolund PTAL

suppressed at default verbosity. Ensure the warning is
always visible through crun_error_write_warning_and_release. */
if (ret > 0)
crun_make_error (err, 0, "poststop hook failed with exit code: %d", ret);
Copy link
Member

Choose a reason for hiding this comment

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

we set the error only when the function is also reporting the failure.

If we want to set err, then we need to return a negative value

Copy link
Contributor

Choose a reason for hiding this comment

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

Another thing: If there are multiple poststop hooks, they are not treated the same because keep_going is true. The last hook is treated differently as it influences the return value from do_hooks().
See

if (keep_going)
libcrun_warning ("error executing hook `%s` (exit code: %d)", hooks[i]->path, ret);
else
{
libcrun_error (0, "error executing hook `%s` (exit code: %d)", hooks[i]->path, ret);
break;
}

@jnovy jnovy force-pushed the fix/issue-1946 branch 2 times, most recently from 4792bbd to 49f6e1f Compare March 16, 2026 13:16
FILE *out = context->output_handler_arg;
if (out == NULL)
out = stderr;
fprintf (out, "poststop hook failed with exit code: %d\n", ret);
Copy link
Member

Choose a reason for hiding this comment

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

please use libcrun_warning here (or libcrun_error)

@packit-as-a-service
Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

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

LGTM

@giuseppe
Copy link
Member

it misses the Signed-off-by line

When do_hooks() returns a positive exit code (hook process exited
non-zero), the error object is not set, so the existing check for
ret < 0 silently ignores the failure. Change the condition to
ret != 0 and, for positive return values, directly write the warning
message to the output stream without using crun_make_error(), which
would violate the convention that err is only set when the function
returns a negative value.

For ret < 0 (internal errors where err is already set by do_hooks),
the existing crun_error_write_warning_and_release() path is preserved.

Add two tests verifying that single and multiple failing poststop
hooks produce the expected warning message.

Fixes: containers#1946
Signed-off-by: Jindrich Novy <jnovy@redhat.com>
@jnovy
Copy link
Contributor Author

jnovy commented Mar 16, 2026

@eriksjolund While it's true that do_hooks() only returns the last hook's ret, each individual hook failure is logged immediately via libcrun_warning() when keep_going=true:

if (keep_going)
    libcrun_warning ("error executing hook `%s` (exit code: %d)", hooks[i]->path, ret);

So no failure is silently lost — every failing hook produces a visible warning at the time it fails.

This also aligns with the OCI runtime spec (lifecycle step 13):

The poststop hooks MUST be invoked by the runtime. If any poststop hook fails, the runtime MUST log a warning, but the remaining hooks and lifecycle continue as if the hook had succeeded.

The current implementation satisfies both requirements: warnings are emitted per-hook, and execution continues. The final return value being from the last hook is acceptable because the caller (container_delete_internal) already ignores the return value of the poststop hooks call - it's a best-effort operation during container cleanup.

Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

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

LGTM

@kolyshkin kolyshkin merged commit be4c43c into containers:main Mar 16, 2026
48 checks passed
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.

Warning is never printed when using a poststop hook with /bin/non-existent

4 participants