From d8f86dff810e9974ec9bf62dc61d870131666095 Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 13:54:14 +0800
Subject: [PATCH 1/9] run: honor --no-block for trigger units
Reproducer:
unit=run-nb-trigger-$(date +%s)
systemd-run --no-block --collect --unit="$unit" \
--socket-property=ListenStream=/proc/systemd-run-repro/socket \
/usr/bin/true
echo $?
Before, systemd-run still waited for the trigger unit job and
propagated the socket start failure. With --no-block it should only
verify and enqueue the request, as the service path already does.
Follow-up for 3d161f991e16369aa59f447eb4cdb90af33261c8
---
src/run/run.c | 18 +++++++++++-------
test/units/TEST-74-AUX-UTILS.run.sh | 7 +++++++
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/src/run/run.c b/src/run/run.c
index 63c0718ad9..ac6d3c0196 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -2808,9 +2808,11 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
assert(bus);
assert(suffix);
- r = bus_wait_for_jobs_new(bus, &w);
- if (r < 0)
- return log_error_errno(r, "Could not watch jobs: %m");
+ if (!arg_no_block) {
+ r = bus_wait_for_jobs_new(bus, &w);
+ if (r < 0)
+ return log_error_errno(r, "Could not watch jobs: %m");
+ }
if (arg_unit) {
switch (unit_name_to_type(arg_unit)) {
@@ -2875,10 +2877,12 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
if (r < 0)
return bus_log_parse_error(r);
- r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR,
- arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL);
- if (r < 0)
- return r;
+ if (w) {
+ r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR,
+ arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL);
+ if (r < 0)
+ return r;
+ }
if (!arg_quiet) {
log_info("Running %s as unit: %s", suffix + 1, trigger);
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index bdb0fd1a7e..ec73a0bd1c 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -205,6 +205,13 @@ grep -q "^SocketMode=0644$" "/run/systemd/transient/$UNIT.socket"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT.socket" "$UNIT.service" || :
+UNIT="socket-no-block-$RANDOM"
+systemd-run --no-block --collect \
+ --unit="$UNIT" \
+ --socket-property=ListenStream=/proc/systemd-run-repro/socket \
+ true
+systemctl stop "$UNIT.socket" "$UNIT.service" || :
+
: "Job mode"
systemd-run --job-mode=help
(! systemd-run --job-mode=foo --scope true)
From dd6c127d6bb55c3280c1344c7d8222e2aad8511f Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 13:56:52 +0800
Subject: [PATCH 2/9] run: reject --ignore-failure in scope mode
Reproducer:
sudo systemd-run --scope --ignore-failure /bin/false
echo $?
Before, the option was accepted but had no effect because scope mode
executes the command locally after creating the scope. The flag is only
encoded into service ExecStart properties, so accept it only where it can
be applied.
Follow-up for 1072d9473123ed174e033704fc5222216b655c9e
---
man/systemd-run.xml | 2 +-
src/run/run.c | 4 ++--
test/units/TEST-74-AUX-UTILS.run.sh | 1 +
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index 386702ac40..9afe4ab446 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -575,7 +575,7 @@
By default, if the specified command fails the invoked unit will be marked failed
(though possibly still unloaded, see , above), and this is reported in the
logs. If this switch is specified this is suppressed and any non-success exit status/code of the
- command is treated as success.
+ command is treated as success. This option is not supported in mode.
diff --git a/src/run/run.c b/src/run/run.c
index ac6d3c0196..69449ab2ff 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -697,9 +697,9 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Scope execution is not supported on remote systems.");
- if (arg_scope && (arg_remain_after_exit || arg_service_type))
+ if (arg_scope && (arg_remain_after_exit || arg_service_type || arg_ignore_failure))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--remain-after-exit and --service-type= are not supported in --scope mode.");
+ "--remain-after-exit, --service-type= and --ignore-failure are not supported in --scope mode.");
if (arg_scope && arg_no_block)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index ec73a0bd1c..9db1a81018 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -115,6 +115,7 @@ systemd-run --scope \
--property=RuntimeMaxSec=10 \
--property=RuntimeMaxSec=infinity \
true
+(! systemd-run --scope --ignore-failure false)
: "Transient scope (user daemon)"
# FIXME: https://github.com/systemd/systemd/issues/27883
From eeb8ef03665bf5413c3b0a14a55e334f08ce82bc Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 14:05:52 +0800
Subject: [PATCH 3/9] run: accept explicit trigger unit names
Reproducer:
unit=run-explicit-path-$(date +%s)
systemd-run --unit="$unit.path" \
--path-property=PathExists=/tmp \
/bin/true
echo $?
Before, an explicit .path unit name was not recognized as the
trigger unit. It was mangled again as a service name, so PID 1 rejected
the transient request with an already-loaded unit conflict.
Follow-up for d59ef3e24362aa7a9e209ed07db5feca1a2cdb8e
---
src/run/run.c | 11 ++++++++++-
test/units/TEST-74-AUX-UTILS.run.sh | 22 ++++++++++++++++++++++
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/src/run/run.c b/src/run/run.c
index 69449ab2ff..2fffa66301 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -2815,7 +2815,9 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
}
if (arg_unit) {
- switch (unit_name_to_type(arg_unit)) {
+ UnitType t = unit_name_to_type(arg_unit);
+
+ switch (t) {
case UNIT_SERVICE:
service = strdup(arg_unit);
@@ -2827,7 +2829,14 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
return log_error_errno(r, "Failed to change unit suffix: %m");
break;
+ case UNIT_PATH:
+ case UNIT_SOCKET:
case UNIT_TIMER:
+ if (!streq(suffix + 1, unit_type_to_string(t)))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Specified unit '%s' does not match requested %s trigger.",
+ arg_unit, suffix + 1);
+
trigger = strdup(arg_unit);
if (!trigger)
return log_oom();
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index 9db1a81018..5a67ef3f10 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -189,6 +189,17 @@ grep -q "^PathChanged=/root/bar$" "/run/systemd/transient/$UNIT.path"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT.path" "$UNIT.service" || :
+UNIT="path-explicit-$RANDOM.path"
+SERVICE="${UNIT%.path}.service"
+systemd-run --remain-after-exit \
+ --unit="$UNIT" \
+ --path-property=PathExists=/tmp \
+ true
+systemctl cat "$UNIT" "$SERVICE"
+grep -q "^PathExists=/tmp$" "/run/systemd/transient/$UNIT"
+grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$SERVICE"
+systemctl stop "$UNIT" "$SERVICE" || :
+
: "Transient socket unit"
UNIT="socket-0-$RANDOM"
systemd-run --remain-after-exit \
@@ -206,6 +217,17 @@ grep -q "^SocketMode=0644$" "/run/systemd/transient/$UNIT.socket"
grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT.socket" "$UNIT.service" || :
+UNIT="socket-explicit-$RANDOM.socket"
+SERVICE="${UNIT%.socket}.service"
+systemd-run --remain-after-exit \
+ --unit="$UNIT" \
+ --socket-property=ListenFIFO=/tmp/socket-explicit.fifo \
+ true
+systemctl cat "$UNIT" "$SERVICE"
+grep -q "^ListenFIFO=/tmp/socket-explicit.fifo$" "/run/systemd/transient/$UNIT"
+grep -qE "^ExecStart=.*true.*$" "/run/systemd/transient/$SERVICE"
+systemctl stop "$UNIT" "$SERVICE" || :
+
UNIT="socket-no-block-$RANDOM"
systemd-run --no-block --collect \
--unit="$UNIT" \
From df3a08b244eaed95a7869b590e43180ce1572c85 Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 14:12:30 +0800
Subject: [PATCH 4/9] run: reset groups before scope uid switch
Reproducer:
sudo systemd-run --scope --uid=nobody /usr/bin/id
Before, the command ran as nobody but kept the caller supplementary
root group, for example groups=65534(nogroup),0(root). Scope mode
performs the uid/gid switch locally, so initialize the target user groups
before dropping privileges.
Follow-up for 4de33e7f3238a6fe616e61139ab87e221572e5e5
---
src/run/run.c | 52 +++++++++++++++++++++--------
test/units/TEST-74-AUX-UTILS.run.sh | 4 +++
2 files changed, 43 insertions(+), 13 deletions(-)
diff --git a/src/run/run.c b/src/run/run.c
index 2fffa66301..5d95ee8a22 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -2517,6 +2517,17 @@ static int start_transient_service(sd_bus *bus) {
return EXIT_SUCCESS;
}
+static int log_scope_group_setup_errno(int r, gid_t gid, const char *message) {
+ assert(r < 0);
+ assert(message);
+
+ if (!ERRNO_IS_PRIVILEGE(r) || gid != getgid())
+ return log_error_errno(r, "%s: %m", message);
+
+ log_debug_errno(r, "%s, ignoring: %m", message);
+ return 0;
+}
+
static int start_transient_scope(sd_bus *bus) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
@@ -2623,26 +2634,22 @@ static int start_transient_scope(sd_bus *bus) {
return log_error_errno(errno, "Failed to set nice level: %m");
}
+ gid_t gid = GID_INVALID;
if (arg_exec_group) {
- gid_t gid;
-
r = get_group_creds(arg_exec_group, /* flags= */ 0, /* ret_name= */ NULL, &gid);
if (r < 0)
return log_error_errno(r, "Failed to resolve group '%s': %s",
arg_exec_group, STRERROR_GROUP(r));
-
- if (setresgid(gid, gid, gid) < 0)
- return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
}
+ uid_t uid = UID_INVALID;
if (arg_exec_user) {
_cleanup_free_ char *user = NULL, *home = NULL, *shell = NULL;
- uid_t uid;
- gid_t gid;
+ gid_t user_gid;
r = get_user_creds(arg_exec_user,
USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS,
- &user, &uid, &gid, &home, &shell);
+ &user, &uid, &user_gid, &home, &shell);
if (r < 0)
return log_error_errno(r, "Failed to resolve user '%s': %s",
arg_exec_user, STRERROR_USER(r));
@@ -2669,14 +2676,33 @@ static int start_transient_scope(sd_bus *bus) {
if (r < 0)
return log_oom();
- if (!arg_exec_group &&
- setresgid(gid, gid, gid) < 0)
- return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
+ if (!gid_is_valid(gid))
+ gid = user_gid;
- if (setresuid(uid, uid, uid) < 0)
- return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid);
+ r = initgroups_wrapper(arg_exec_user, gid);
+ if (r < 0) {
+ r = log_scope_group_setup_errno(
+ r,
+ gid,
+ strjoina("Failed to initialize supplementary groups for user '", arg_exec_user, "'"));
+ if (r < 0)
+ return r;
+ }
+ } else if (gid_is_valid(gid)) {
+ r = maybe_setgroups(/* size= */ 0, /* list= */ NULL);
+ if (r < 0) {
+ r = log_scope_group_setup_errno(r, gid, "Failed to drop supplementary groups");
+ if (r < 0)
+ return r;
+ }
}
+ if (gid_is_valid(gid) && setresgid(gid, gid, gid) < 0)
+ return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
+
+ if (uid_is_valid(uid) && setresuid(uid, uid, uid) < 0)
+ return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid);
+
if (arg_working_directory && chdir(arg_working_directory) < 0)
return log_error_errno(errno, "Failed to change directory to '%s': %m", arg_working_directory);
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index 5a67ef3f10..acd7bc5b6c 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -111,6 +111,10 @@ systemd-run --scope --slice-inherit --slice=foo \
bash -xec '[[ "$(
Date: Mon, 13 Jul 2026 14:14:33 +0800
Subject: [PATCH 5/9] run: reject JSON output with stdio forwarding
Reproducer:
sudo systemd-run --wait --pipe --json=short /bin/echo hi
Before, systemd-run wrote its JSON metadata to stdout and then passed
the command stdout through the same stream. The combined output was not
valid machine-readable JSON, so reject the conflicting modes.
Follow-up for fe5a6c47af675bc0020c545d86fb103492e1d77c
---
src/run/run.c | 4 ++++
test/units/TEST-74-AUX-UTILS.run.sh | 1 +
2 files changed, 5 insertions(+)
diff --git a/src/run/run.c b/src/run/run.c
index 5d95ee8a22..6754b80bcf 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -723,6 +723,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--pty-late is not compatible with --service-type=oneshot.");
+ if (sd_json_format_enabled(arg_json_format_flags) && arg_stdio != ARG_STDIO_NONE)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not compatible with --pty/--pty-late/--pipe.");
+
if (arg_scope && with_trigger)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Path, socket or timer options are not supported in --scope mode.");
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index acd7bc5b6c..a5c79ddb72 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -256,6 +256,7 @@ systemd-run --wait --pty true
systemd-run --wait --machine=.host --pty true
systemd-run --json=short true | jq . >/dev/null
systemd-run --json=pretty true | jq . >/dev/null
+(! systemd-run --wait --pipe --json=short true)
(! SHELL=/bin/false systemd-run --quiet --shell)
(! systemd-run)
From 3fa641d01fc559a4d1c73dcc72bdc7ebe9d08200 Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 14:16:55 +0800
Subject: [PATCH 6/9] run: reject JSON output for trigger units
Reproducer:
unit=run-json-trigger-$(date +%s)
sudo systemd-run --json=short --unit="$unit" \
--on-active=30s \
/bin/true
Before, trigger mode accepted --json=short but printed only human-readable
"Running timer as unit" and "Will run service" lines. Reject the option
until trigger mode has structured output.
Follow-up for fe5a6c47af675bc0020c545d86fb103492e1d77c
---
src/run/run.c | 4 ++++
test/units/TEST-74-AUX-UTILS.run.sh | 1 +
2 files changed, 5 insertions(+)
diff --git a/src/run/run.c b/src/run/run.c
index 6754b80bcf..e49e918b0f 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -735,6 +735,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--timer-property= has no effect without any other timer options.");
+ if (sd_json_format_enabled(arg_json_format_flags) && with_trigger)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not compatible with path, socket or timer operations.");
+
if (arg_wait) {
if (arg_no_block)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index a5c79ddb72..0619a8995f 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -257,6 +257,7 @@ systemd-run --wait --machine=.host --pty true
systemd-run --json=short true | jq . >/dev/null
systemd-run --json=pretty true | jq . >/dev/null
(! systemd-run --wait --pipe --json=short true)
+(! systemd-run --json=short --on-active=30s true)
(! SHELL=/bin/false systemd-run --quiet --shell)
(! systemd-run)
From 2a4750c3e1022eff2a7a5006562e881fecb64f93 Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 14:18:42 +0800
Subject: [PATCH 7/9] run: reject JSON output in scope mode
Reproducer:
sudo systemd-run --scope --json=short /bin/echo hi
Before, systemd-run printed JSON metadata to stdout and then executed
the scope command on the same stdout. The combined stream was not valid
JSON, so reject --json= in scope mode.
Follow-up for fe5a6c47af675bc0020c545d86fb103492e1d77c
---
src/run/run.c | 4 ++++
test/units/TEST-74-AUX-UTILS.run.sh | 1 +
2 files changed, 5 insertions(+)
diff --git a/src/run/run.c b/src/run/run.c
index e49e918b0f..57f2e66303 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -705,6 +705,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--no-block is not supported in --scope mode.");
+ if (arg_scope && sd_json_format_enabled(arg_json_format_flags))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not supported in --scope mode.");
+
if (arg_stdio != ARG_STDIO_NONE) {
if (with_trigger || arg_scope)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index 0619a8995f..7d7b95687a 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -120,6 +120,7 @@ systemd-run --scope \
--property=RuntimeMaxSec=infinity \
true
(! systemd-run --scope --ignore-failure false)
+(! systemd-run --scope --json=short true)
: "Transient scope (user daemon)"
# FIXME: https://github.com/systemd/systemd/issues/27883
From 7e76e931420fbd5aa1390947c084a92661b1e709 Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 14:21:02 +0800
Subject: [PATCH 8/9] run: reject JSON output with verbose logs
Reproducer:
sudo systemd-run --wait --verbose --json=short /bin/echo hi
Before, systemd-run printed JSON metadata to stdout while --verbose
also spawned journalctl output on stdout. The resulting stream mixed JSON
with journal lines, so reject the conflicting options.
Follow-up for 744ca8f616b98f579d930176f6262e1ac197840b
---
man/systemd-run.xml | 4 ++++
src/run/run.c | 30 +++++++++++++++++------------
test/units/TEST-74-AUX-UTILS.run.sh | 1 +
3 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index 9afe4ab446..d0eec8cf0d 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -616,6 +616,10 @@
+ may not be combined with ,
+ , , ,
+ , or path, socket, or timer options.
+
All command line arguments after the first non-option argument become part of the command line of
the launched process.
diff --git a/src/run/run.c b/src/run/run.c
index 57f2e66303..dd5a207455 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -705,10 +705,6 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--no-block is not supported in --scope mode.");
- if (arg_scope && sd_json_format_enabled(arg_json_format_flags))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--json= is not supported in --scope mode.");
-
if (arg_stdio != ARG_STDIO_NONE) {
if (with_trigger || arg_scope)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -727,22 +723,32 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--pty-late is not compatible with --service-type=oneshot.");
- if (sd_json_format_enabled(arg_json_format_flags) && arg_stdio != ARG_STDIO_NONE)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--json= is not compatible with --pty/--pty-late/--pipe.");
-
if (arg_scope && with_trigger)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Path, socket or timer options are not supported in --scope mode.");
+ if (sd_json_format_enabled(arg_json_format_flags)) {
+ if (arg_scope)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not supported in --scope mode.");
+
+ if (arg_stdio != ARG_STDIO_NONE)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not compatible with --pty/--pty-late/--pipe.");
+
+ if (arg_verbose)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not compatible with --verbose.");
+
+ if (with_trigger)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--json= is not compatible with path, socket or timer operations.");
+ }
+
if (arg_timer_property && !arg_with_timer)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--timer-property= has no effect without any other timer options.");
- if (sd_json_format_enabled(arg_json_format_flags) && with_trigger)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--json= is not compatible with path, socket or timer operations.");
-
if (arg_wait) {
if (arg_no_block)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index 7d7b95687a..c85117e135 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -259,6 +259,7 @@ systemd-run --json=short true | jq . >/dev/null
systemd-run --json=pretty true | jq . >/dev/null
(! systemd-run --wait --pipe --json=short true)
(! systemd-run --json=short --on-active=30s true)
+(! systemd-run --wait --verbose --json=short true)
(! SHELL=/bin/false systemd-run --quiet --shell)
(! systemd-run)
From 06247b0804fcd75f26a329b35ec92033f6c56bd7 Mon Sep 17 00:00:00 2001
From: dongshengyuan <545258830@qq.com>
Date: Mon, 13 Jul 2026 14:23:26 +0800
Subject: [PATCH 9/9] run: reject waiting for remain-after-exit services
Reproducer:
unit=run-wait-rae-$(date +%s)
sudo timeout 3s systemd-run --wait --remain-after-exit \
--unit="$unit" /bin/true
echo $?
systemctl is-active "$unit.service"
Before, the command timed out with exit status 124 while the service
stayed active. --wait waits for deactivation, but RemainAfterExit=yes
keeps the service active after the command exits.
Follow-up for 2a453c2ee3090e1942bfd962262f3eff0adbfa97
---
man/systemd-run.xml | 9 ++++++---
src/run/run.c | 8 ++++++++
test/units/TEST-74-AUX-UTILS.run.sh | 2 ++
3 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index d0eec8cf0d..e9c6384363 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -216,7 +216,9 @@
After the service process has terminated, keep the service around until it is explicitly
stopped. This is useful to collect runtime information about the service after it finished running. Also see
RemainAfterExit= in
- systemd.service5.
+ systemd.service5. This
+ option may not be combined with , , ,
+ , or .
@@ -529,8 +531,9 @@
completed). On exit, terse information about the unit's runtime is shown, including total runtime (as well as
CPU, memory, IO, and IP accounting data, if the corresponding cgroup accounting settings are enabled)
and the exit code and status of the main process. This output may be suppressed with .
- This option may not be combined with , or the various
- path, socket, or timer options.
+ This option may not be combined with ,
+ , or the various path, socket, or timer
+ options.
diff --git a/src/run/run.c b/src/run/run.c
index dd5a207455..85256a2b1b 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -717,6 +717,10 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_no_block)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--pty/--pty-late/--pipe is not compatible with --no-block.");
+
+ if (arg_remain_after_exit)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--pty/--pty-late/--pipe is not compatible with --remain-after-exit.");
}
if (arg_stdio == ARG_STDIO_PTY && arg_pty_late && streq_ptr(arg_service_type, "oneshot"))
@@ -761,6 +765,10 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_scope)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--wait may not be combined with --scope.");
+
+ if (arg_remain_after_exit)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--wait may not be combined with --remain-after-exit.");
}
if (arg_scope && arg_root_directory)
diff --git a/test/units/TEST-74-AUX-UTILS.run.sh b/test/units/TEST-74-AUX-UTILS.run.sh
index c85117e135..1c0efdf08f 100755
--- a/test/units/TEST-74-AUX-UTILS.run.sh
+++ b/test/units/TEST-74-AUX-UTILS.run.sh
@@ -72,6 +72,8 @@ systemctl cat "$UNIT"
grep -q "^Type=oneshot" "/run/systemd/transient/$UNIT.service"
systemctl stop "$UNIT"
(! systemctl cat "$UNIT")
+(! systemd-run --wait --remain-after-exit true)
+(! systemd-run --pipe --remain-after-exit true)
: "Transient service (user daemon)"
systemd-run --wait --pipe --user --machine=testuser@ \