Fix Docker container hanging after one-shot commands - #2280
Merged
Conversation
Every authentication branch in the entrypoint ended with an unconditional `sleep infinity`, so a container invoked with a one-shot command printed its result and then never exited. That made the image unusable from scripts, since the command's exit status never propagated, and there was no way to opt out. Only failing commands exited, and then only incidentally, via `set -e`. Replace the eight unconditional `sleep infinity` calls with a single lifecycle decision: stay resident only for service mode, for an invocation with no command, or when KEEPER_KEEP_ALIVE is set; otherwise exit with the wrapped command's status. Three further problems in the same lifecycle code: - The container ignored SIGTERM. Bash defers trap handlers while a foreground child runs, so `sleep infinity` kept the existing EXIT/INT/TERM trap from ever firing: `docker stop` waited out the full grace period and then SIGKILLed, leaving the KSM config monitor uncleaned. Idle via a backgrounded sleep and `wait`, with explicit TERM/INT handlers. - Command arguments were flattened into a single string and re-split by word splitting, so any argument containing spaces (record titles, notes, search queries) reached Commander as several arguments. Carry them in an array. - The KSM one-shot path no longer starts the perpetual config monitor. It uploads config.json once so refreshed device state persists, then exits. Fixes #2264
sk-keeper
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every authentication branch in
docker-entrypoint.shended with an unconditionalsleep infinity, so a container invoked with a one-shot command printed its result and then never exited. Replaced the eight unconditional calls with a single lifecycle decision: stay resident only for service mode, for an invocation with no command, or whenKEEPER_KEEP_ALIVEis set; otherwise exit with the wrapped command's status.Three further problems in the same lifecycle code:
sleep infinitykept the existingEXIT INT TERMtrap from ever firing.docker stopwaited out the full grace period and then SIGKILLed (exit 137), leaving the KSM config monitor uncleaned. Now idles via a backgrounded sleep andwait, with explicit TERM/INT handlers.get "My Record Title"reached Commander as three arguments. Now carried in an array.config.jsononce, so refreshed device state still persists, then exits.Context
Reported in #2264:
docker run ... keeper/commander get --format password <uid>prints the password, then hangs forever, making the image unusable in scripts because the exit status never propagates.Note that before this change only failing commands exited, and then only incidentally via
set -eaborting beforesleep infinity— so a valid record hung while an invalid one exited.Behavior verified before/after, with a stubbed
keeper.pyto exercise the lifecycle logic and against the built image for the end-to-end case:get "My Record Title"service-createdocker stopFixes #2264