One xargs Command Failed. Later Inputs Still Ran.
The batch is red. GNU xargs returned 123, the worker reported a failure for "B space", and the caller has a nonzero status to inspect. Yet the effect ledger contains ["A", "C\nline", "-D"]. Two later inputs ran after the failed one. The error was reported; the assumed stopping policy was not implemented.
That is the central result of the supplied nine-case commissioning fixture. Its identifiers are synthetic arguments, not paths to modify. In JSON, "C\nline" represents one identifier containing a real newline. Preserving that identity matters because an apparent four-line display is not reliable evidence of four arguments.
This playbook separates three questions: did the worker fail, did xargs launch subsequent invocations, and which effects remain? An aggregate exit status answers only part of that review. The repair deliberately maps a selected application failure to child exit 255, retains its original code, and qualifies the resulting serial scheduling stop through an exact ledger.
All nine cases use -P1 in a new, owned Linux results directory. The only effects are local evidence records. There is no deletion, service call, parallel worker, or claim of transactional rollback. The outcome is a bounded decision about future invocations, followed by a separate recovery decision about work already performed.
State whether the batch must continue or stop
Write the intended policy before running the candidate. For this experiment, the ordered input manifest is ["A", "B space", "C\nline", "-D"]. If B fails, no worker invocation containing either later identifier should begin. A successful earlier effect may remain. That last sentence excludes an all-or-nothing promise that this scheduler cannot establish.
Continuation can be a legitimate design for an independently approved batch. It is not the design being qualified here. A reviewer must not silently change the requirement from “stop later serial scheduling” to “eventually return an error” after seeing that the latter happens to pass.
The GNU aggregate exit-status reference, preserved in DESY's archived 4.4.0 manual, distinguishes the two child results tested here: child exit 1 contributes to xargs status 123, while child exit 255 produces 124. The 4.10.0 fixture below checks these same mappings directly. Neither aggregate identifies completed items, so the experiment retains invocation and effect events together in each case's ledger.
Use REPAIR when a candidate advertised as fail-fast starts C or -D after B fails. Use ACCEPT_BOUNDED only for the admitted serial remapping policy with the required evidence. Use RECONCILE before deciding what can be replayed, and HOLD when evidence or operating conditions cannot support that decision.
Bash pipeline status is a separate layer: that related problem concerns conveying failure through a logging pipeline. Here the negative control already returns failure. Recovering a lost status is not the missing repair; preventing the next invocation is. Keep those acceptance questions separate in both the test name and the operational review.
Freeze the GNU environment and authored input list
The supplied commissioning run on October 15, 2026 recorded GNU findutils xargs 4.10.0, CPython 3.13.5, and GNU Bash 5.2.37(1)-release (x86_64-pc-linux-gnu). Its platform string was Linux-6.18.44-x86_64-with-glibc2.41. These are observed runtime labels, not assertions that any component is the latest release.
The complete driver below records those values again on each run. It verifies that the located xargs identifies itself as GNU findutils, but it does not enforce an exact version pin. A different version in a new report must be treated as a different execution environment, not silently assigned the supplied run's provenance. The driver records tool versions, not a full package or inherited-environment manifest. Keep the report and the exact saved source together when comparing runs.
The GNU xargs manual defines -0 as NUL-separated input and -P1 as the selected one-process-at-a-time limit. Here -n1 admits at most one input item per child; X6 changes that grouping to -n2. Fixed command arguments, such as the script and ledger name, are separate from the authored input items.
Preserve arguments without claiming filesystem safety
The list contains an ordinary identifier, a space-containing identifier, a newline-containing identifier, and a leading-hyphen identifier. The driver encodes each as UTF-8 followed by one NUL byte. Its worker receives -D as an ordinary item because the worker's own positional parsing treats it that way.
No item is interpreted as a path to open, delete, or rename. The worker writes only its assigned JSONL ledger; the driver creates the owned results directory and report. NUL separation protects these argument boundaries. It does not validate arbitrary command semantics or make changing external filesystem targets safe.
That boundary distinguishes this test from selection before destructive actions. The present list is authored in advance, not discovered through traversal. Freeze the list, worker, flags, and environment together so an argument-identity defect cannot masquerade as a scheduling result.
Run the complete nine-case scheduling laboratory
Save the complete source below as xargs_fixture.py in a writable directory you control, then run python xargs_fixture.py. The program invokes its own worker mode, so no second script or attachment is needed. Each attempt creates a new directory beside the source whose name begins with xargs-evidence-. Keep that directory, including unsuccessful attempts, until the review is complete.
The source SHA-256 is 19e8f383ee1195c74c0dbfdb421d42bca6e53a4a19847d0628bf082d7bfda396. It is the unchanged driver from the supplied commissioning fixture. Save it as UTF-8 without a byte-order mark, using LF line endings and one final newline. Preserve indentation, ASCII flags, and quoted literals. A different byte digest identifies a different saved source artifact; an edited copy cannot inherit this source's execution claim.
The worker records an invocation event before processing its items. For each item it records start, then either failure with original_code or the synthetic effect. B's selected failure occurs before its effect record. In map mode, the original failure is written first and the worker then returns 255. Nothing in that branch removes A's earlier record.
The fixed specs table declares expectations before reading any observed ledger. It supplies each case's inputs, child failure, remapping policy, group size, extra flags, wrapper choice, expected aggregate, expected effects, and invocation count. It does not manufacture an expected answer from the process output.
The launch uses an argument list, not interpolated shell command text. The only intentional shell boundary is X7. Its command string is fixed, and the xargs argument vector is passed through "$@"; wrapper occupies the shell's command-name position rather than consuming the xargs executable as an input item.
"""GNU xargs, serial synthetic effects only. No user files are operated on."""
import hashlib
import json
import platform
import shutil
import subprocess
import sys
import tempfile
from datetime import datetime,timezone
from pathlib import Path
IDS=['A','B space','C\nline','-D']
def append(path,entry):
with open(path,'a',encoding='utf-8') as stream:
stream.write(json.dumps(entry)+'\n')
def worker(args):
log,fail,remap,*items=args
append(log,{'event':'invocation','items':items})
for ident in items:
append(log,{'event':'start','id':ident})
code=int(fail) if ident=='B space' else 0
if code:
append(log,{'event':'failure','id':ident,'original_code':code})
return 255 if remap=='map' else code
append(log,{'event':'effect','id':ident})
return 0
def require(ok,message):
if not ok:
raise RuntimeError(message)
def main():
folder=Path(tempfile.mkdtemp(prefix='xargs-evidence-',dir=Path(__file__).parent)).resolve()
binary=shutil.which('xargs'); bash=shutil.which('bash')
require(binary is not None and bash is not None,'Need GNU xargs and Bash')
version=subprocess.run([binary,'--version'],capture_output=True,text=True,check=True).stdout
require('GNU findutils' in version,'Not the qualified implementation')
report={'status':'FAIL','started_utc':datetime.now(timezone.utc).isoformat(),
'python':platform.python_version(),'platform':platform.platform(),'xargs':version.splitlines()[0],
'bash':subprocess.run([bash,'--version'],capture_output=True,text=True,check=True).stdout.splitlines()[0],
'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),'cases':[]}
# name, child failure, remapping, group size, extra flags, input, outer errexit,
# expected xargs/wrapper code, expected effects, invocation count
specs=[
('X1_success',0,'raw',1,[],IDS,False,0,IDS,4),
('X2_exit_one',1,'raw',1,[],IDS,False,123,['A','C\nline','-D'],4),
('X3_size_flag',1,'raw',1,['-x'],IDS,False,123,['A','C\nline','-D'],4),
('X4_exit_255',255,'raw',1,[],IDS,False,124,['A'],2),
('X5_map_failure',1,'map',1,[],IDS,False,124,['A'],2),
('X6_two_per_child',1,'raw',2,[],IDS,False,123,['A','C\nline','-D'],2),
('X7_outer_errexit',1,'raw',1,[],IDS,True,123,['A','C\nline','-D'],4),
('X8_empty_default',0,'raw',1,[],[],False,0,[],1),
('X9_empty_no_run',0,'raw',1,['-r'],[],False,0,[],0)]
try:
for name,fail,remap,size,extra,inputs,outer,code,effects,calls in specs:
row={'id':name,'status':'FAIL'}; report['cases'].append(row)
log=folder/(name+'.jsonl')
command=[binary,'-0','-P','1','-n',str(size),*extra,sys.executable,
str(Path(__file__).resolve()),'--worker',str(log),str(fail),remap]
if outer:
command=[bash,'--noprofile','--norc','-e','-c',
'"$@"; printf "PARENT_AFTER\\n"','wrapper',*command]
data=b''.join(x.encode('utf-8')+b'\0' for x in inputs)
proc=subprocess.run(command,input=data,capture_output=True,timeout=15)
ledger=[json.loads(x) for x in log.read_text().splitlines()] if log.exists() else []
observed_effects=[x['id'] for x in ledger if x['event']=='effect']
invocations=[x['items'] for x in ledger if x['event']=='invocation']
row.update(command=command,input_ids=inputs,exit_status=proc.returncode,
stdout=proc.stdout.decode(),stderr=proc.stderr.decode(),ledger=ledger,
expected_effects=effects,expected_exit=code,invocations=invocations)
require(proc.returncode==code and observed_effects==effects and len(invocations)==calls,
name+': unexpected scheduling/effects')
if outer:
require('PARENT_AFTER' not in row['stdout'],'Outer errexit did not stop next statement')
if remap=='map':
require(any(x.get('original_code')==1 for x in ledger),'Original code lost')
row['status']='PASS'
require(len(report['cases'])==9,'Missing case')
report['status']='PASS_9_CASES'
except Exception as error:
report['error']={'type':type(error).__name__,'message':str(error)}
raise
finally:
report['finished_utc']=datetime.now(timezone.utc).isoformat()
target=folder/'results.json'; target.write_text(json.dumps(report,indent=2)+'\n')
print(target)
if name=='__main__':
if len(sys.argv)>1 and sys.argv[1]=='--worker':
sys.exit(worker(sys.argv[2:]))
main()
For measured calls, Python's subprocess.run receives NUL-delimited bytes on stdin, captures stdout and stderr, and returns a CompletedProcess whose returncode is the xargs status, or the outer Bash status in X7. Nonzero measured results are expected observations, so these calls do not use check=True. The version probes do use that option because failing to identify the tools is a setup failure, not a diagnostic case.
For completed calls, the report retains the command vector, input IDs, decoded output, full parsed ledger, invocation groups, and expected status and effects. The original JSONL files remain beside it. Read those records as structured data: splitting a displayed identifier at its embedded newline would destroy the very identity the fixture preserves.
The embedded assertions compare aggregate status, exact ordered effects, and invocation count. X7 also checks that PARENT_AFTER is absent; X5 checks that original code 1 remains recorded. They do not independently assert every invocation identity or every event transition. The acceptance gate therefore requires comparison of the recorded groups and complete ledger against the case matrix and worker contract, not merely inspection of the final PASS label.
The supplied commissioning record reports a run from 07:53:46.994803 UTC to 07:54:04.308396 UTC on October 15, 2026, with PASS_9_CASES. An independent local reproduction of the unchanged source on that date ran from 08:55:35.410275 UTC to 08:55:54.042439 UTC, recorded the same runtime labels and digest, and matched all nine checkpoints. A separate comparison against complete expected ledgers matched every invocation, start, failure, and effect event, including the empty-input cases. These timestamps identify executions, not performance measurements.
If an external execution limit interrupts a repeat before results.json is written, preserve any partial ledgers and record the interruption. Do not count that attempt as successful or reconstruct its missing report from a prior run. Interruption of the surrounding harness is distinct from the worker-level failure mapping and may prevent the driver from reaching finally.
The per-command timeout=15 is an emergency laboratory bound, not the proposed repair. Under Python's subprocess timeout behavior, run() kills and waits for its immediate child before raising TimeoutExpired; here that child is xargs, or Bash in X7. This does not establish cancellation or cleanup of every descendant. The exception is not xargs status 124. The driver's exception path records its type and message when execution can reach finally; a timeout may leave a failed case without completed-call output fields.
A mismatch raises RuntimeError. Setup failures, including inability to create the directory or identify the tools, can occur before the protected reporting block. Output decoding, JSON parsing, or filesystem failures can also prevent complete evidence. A missing, partial, or corrupt report is not an empty successful run. Preserve whatever was written, record the failure, and hold acceptance until a complete attempt supports it.
Read PASS_9_CASES as “the diagnostic expectations matched,” including deliberately unacceptable candidates. The Python driver itself exits zero on that diagnostic success; X5's measured xargs result remains 124. The label does not approve X2's scheduling policy, qualify a real application, or certify durable evidence storage. The worker's local append records are the synthetic effects themselves; they are not stand-ins for a successfully tested external operation.
Observe a normal child failure and later invocations
X1_success establishes the positive control: four one-item invocations, four ordered effects, and aggregate status 0. The space, newline, and leading hyphen survive into the worker's item arrays. That baseline matters because a later absence should be attributed to the selected failure policy, not to a broken input producer.
X2_exit_one changes B's child result to 1. Its invocation groups remain [["A"], ["B space"], ["C\nline"], ["-D"]]. The effect sequence becomes ["A", "C\nline", "-D"], and the aggregate becomes 123. B was invoked and started, then logged a failure with original_code: 1; it did not log an effect.
C and -D are not speculative work inferred from a final count. Their invocation, start, and effect events are present after B's failure in the serial ledger. That is the evidence contradicting the candidate's fail-fast claim. The final error code cannot retrospectively remove those observations.
Failure reporting and stop scheduling are distinct
There are two independent questions. Did the caller receive failure? Yes: X2 returned 123. Did the scheduler stop launching later work when the ordinary failure occurred? No: both later one-item workers ran. A review that checks only the first question would approve the wrong operational contract.
Keep X2 as a negative control after implementing a repair. It should continue to expose continuation under the unchanged ordinary-exit policy. Converting its expected result to the desired stopped ledger would conceal the comparison instead of repairing the candidate.
The disposition is therefore REPAIR for a stop-on-failure requirement, followed by RECONCILE for this completed attempt. The reviewer already knows that A, C, and -D have synthetic effects. Rerunning all four identifiers solely because the aggregate was nonzero would disregard that known state. The failure report is correct; the inference drawn from it was not.
Reject the size-limit flag as a failure policy
X3_size_flag repeats X2 with -x added. B still fails with original code 1; xargs still returns 123; all four one-item workers are invoked; A, C, and -D still record effects. This is a controlled option comparison, not a different worker or a differently ordered input list.
The GNU xargs option definitions give -x a command-size role: it makes xargs exit when the relevant size limit is exceeded. It is not a request to stop after an ordinary application failure. The similarly suggestive long name, --exit, does not expand that documented condition into “exit whenever any child fails.”
This experiment does not create oversized arguments or force command-line exhaustion. Consequently, it provides no measured threshold, platform limit, or observation about how such an exhaustion case would be reported. Its narrower finding is that adding this size-related flag did not change the ordinary-child-failure scheduling outcome.
Inspect a proposed fix at the boundary it actually controls. Input grouping, command construction, worker execution, and parent-shell continuation are different boundaries. An option can legitimately constrain one of them while doing nothing to the later-invocation policy being reviewed.
The acceptance comparison remains exact: X3 has the same groups, effects, and aggregate as X2. Do not replace that comparison with “the option was present” or “the stderr looked serious.” Neither observation demonstrates that C and -D were withheld.
Keep -x only when a separately stated size-construction policy requires it. Reject it as the repair for this fail-fast claim. The useful next experiment changes the child's scheduler-visible status while holding input order, grouping, serial execution, and synthetic effect semantics fixed. That isolates the mechanism under test instead of accumulating plausible-looking flags.
Use child 255 to stop future serial scheduling
X4_exit_255 changes B's worker result to 255 without adding a remapping policy. The supplied and repeated observations are aggregate 124, invocation groups [["A"], ["B space"]], and effects ["A"]. B has an invocation and a failure; neither C nor -D has an invocation event in this completed serial attempt.
The documented child-255 stop is the relevant GNU mechanism. Unlike the ordinary exit used in X2, this status tells xargs to stop processing further input. The case qualifies the visible scheduling consequence under -P1: no later worker invocation is recorded after the selected failure.
Interpret the scope precisely. The ledger measures worker invocations and synthetic effects. It does not measure every byte the scheduler may have buffered, every operating-system action, or arbitrary descendant activity. The application requirement concerns subsequent workers, so that is the boundary the evidence must establish.
Stopping is not rolling back
A's effect remains in X4. It was written before B failed, and neither the worker nor xargs has a branch that removes it. A stopped sequence is therefore not an untouched sequence. The exact accepted effect list is ["A"], not an empty list.
Likewise, 124 is not successful processing. It reports the aggregate condition associated with child 255 in this experiment. It does not say that B succeeded, that A was undone, or that recovery has been authorized. Keep the scheduling decision and the application outcome in separate fields of the review.
X4 demonstrates the raw mechanism; X5 tests the application-policy repair. That distinction matters when a real worker normally returns a different failure code. A reviewer needs to see how the original failure survives the translation, rather than simply replacing the application result with a scheduler signal.
Map a chosen failure without losing its original code
X5_map_failure restores B's original application code to 1 and selects map mode. Before returning, the worker writes a failure event containing "id": "B space" and "original_code": 1. It then returns 255, producing xargs aggregate 124 with the same stopped invocation and effect sequences as X4.
The GNU shell-invocation guidance illustrates explicitly translating a failed command into exit 255 to stop xargs. This fixture implements that scheduling policy inside the Python worker instead of adding another child shell. The choice is deliberate: an application result and a scheduler-control result need not be the same number.
There are three facts to preserve: B's original failure was 1; the worker exposed 255 to xargs; xargs returned 124 to its caller. The failure event, case configuration, retained source, and aggregate together establish that chain. Saving only 124 would lose the original failure, while saving only 1 would not describe the stopping mechanism.
Do not silently broaden this branch into a catch-all exception strategy. In the admitted worker, B's configured nonzero code is the selected failure. Unexpected I/O or logging errors are not explicitly converted by this code. The fixture does not prove that every possible worker fault will produce the intended stop or a complete ledger.
For the bounded repair, require the exact original-code event, two one-item invocations, A's retained effect, and no C or -D events. A 124 with missing failure evidence is HOLD, not proof that the mapping happened correctly. A 124 accompanied by additional effects also contradicts this case's expected state.
The accepted policy remains a failed batch with stopped future serial scheduling. Do not make an outer wrapper return zero merely because the stop mechanism behaved as designed. Operational monitoring still needs the application failure and the unresolved work list; a diagnostic success must not become an application-success signal.
Test where a multi-argument worker failure ends
X6_two_per_child uses -n2 with the ordinary, unmapped failure code 1. Its observed groups are [["A", "B space"], ["C\nline", "-D"]]. The first worker writes A's effect and returns at B's failure. GNU xargs nevertheless starts the second worker, which records effects for C and -D.
The aggregate is 123, just as in X2, and the ordered effect list is also the same. Invocation count, however, drops from four to two. Counting processes alone would therefore miss important structure: two workers handled four logical inputs, and the first worker had already produced one effect before its failing item.
One invocation can contain several logical inputs
The worker controls the loop within one invocation. xargs controls whether another invocation starts. In the source, return leaves the current worker when B fails. It is not a request to xargs to abandon every remaining input group. X6 makes those two control-flow boundaries visible in one ledger.
This exact fixture places B last in its two-item group. It does not experimentally demonstrate a third, unprocessed item inside that same invocation. The source explains that the return ends the loop, but the measured cross-boundary result is the later launch of the separate C/-D worker. Keep source reasoning and observed grouping distinguishable.
Treat -n as a maximum input-argument grouping choice, not as a transaction declaration. For these small arguments, the recorded grouping is exactly two items per worker. Do not infer that arbitrary argument lengths or a changed command must preserve those groups without checking them.
X6 is REPAIR under the selected stop requirement. Preserve both logical-item identity and invocation membership when redesigning it. Simply reducing process count, moving the failure to another function, or describing the worker as a “batch” cannot supply rollback or scheduler-level stopping. Those are separate properties requiring their own evidence.
Keep outer Bash errexit at the correct level
X7_outer_errexit runs the same ordinary-failure xargs command through Bash with --noprofile, --norc, and -e. The fixed shell script executes the supplied command vector, then would print PARENT_AFTER. The recorded wrapper status is 123, and that marker is absent.
Those observations show that the statement after xargs did not run in this wrapper. They do not show that xargs stopped its own later workers. The invocation ledger still contains all four one-item groups, and effects remain ["A", "C\nline", "-D"]. Both observations must survive the review.
The Bash errexit reference documents -e and its context-dependent exceptions. This fixture uses a simple foreground command followed by another statement. It does not test every conditional, function, list, or wrapper context, so the result must not be promoted into a universal claim about all uses of set -e.
--noprofile and --norc do not make an arbitrary inherited environment pristine. The Bash startup rules still allow a noninteractive shell to read a file named by BASH_ENV. Run the unchanged fixture in a controlled environment, with BASH_ENV unset unless its contents are deliberately part of the test. The version report alone does not capture those startup effects.
The ordering explains the apparent contradiction. Bash runs xargs as a command. During that command's execution, xargs launches A, receives B's ordinary failure, and still launches C and -D. Only when xargs returns does the outer shell act on its nonzero aggregate and suppress the following marker.
The source passes the vector through "$@" and assigns wrapper as the shell's command name. It does not assemble the authored IDs into executable shell text. That detail preserves the intended boundary in this controlled example; it is not an assessment of arbitrary untrusted-command security.
Reject two incomplete reviews: one that sees the missing marker and declares serial fail-fast, and one that sees C's effect and declares Bash -e broken. The shell stopped its next statement, while xargs had already continued internally. To repair the latter, change the child-to-xargs policy and verify the invocation ledger again. Additional outer flags cannot retroactively prevent workers that have already completed.
Qualify empty input and preserve effects already made
X8_empty_default supplies zero bytes of input and records one worker invocation with an empty item list: [[]]. The worker writes its invocation event, enters no item iteration, and returns zero. There are no item effects. The aggregate is also zero, but it describes an actual zero-item call, not the absence of execution.
No input can still produce an invocation
The GNU no-run-if-empty option supplies the relevant contrast. X9_empty_no_run adds -r and records no worker invocation: []. Its effect list is empty and aggregate status is zero. Thus equal item-effect counts and equal aggregates can conceal different invocation behavior.
For this worker, the empty invocation is inert apart from its evidence record. A different application might perform initialization or other work before checking its items. That possibility requires a separate worker contract; X8 does not qualify it. Specify whether an empty manifest means “run once without items” or “do not launch the worker.”
Zero input bytes are also distinct from a NUL-delimited empty argument. The latter is not one of the authored cases. Do not extend the empty-input result to inputs that contain empty items without adding explicit expectations and a separately identified test.
Missing evidence is not interchangeable with X9's expected absence. A complete driver report, the successful measured process result, the admitted source, and the zero-invocation expectation must agree. A missing ledger in X8 contradicts its expected call; a missing report for either case cannot establish completion.
For nonempty failed attempts, classify retained effects separately. A remains in X4 and X5; A, C, and -D remain in X2, X3, X6, and X7. B's failure record is not an effect record, and uninvoked later items belong in a different category again. The related lesson that stopping is not undoing earlier writes is useful for recovery discipline, not as a substitute for GNU scheduling documentation.
Reconcile exit status with the entire invocation ledger
Read the matrix across, not down the aggregate column alone. All measured xargs commands use -0 -P1; grouping and exceptional flags are shown separately. “Original to child” distinguishes B's configured application failure from the status exposed by its worker. In X7, the captured aggregate is the outer Bash result.
The arrays use JSON notation, including the escaped newline in "C\nline". Each nested array is one invocation, not an arbitrary display group. These are the supplied run's checkpoints, also matched by the completed repeat execution. They are fixed synthetic expectations, not measurements of a production workload.
Case | Grouping / extra policy | Original to child | Aggregate | Invocation groups | Effects | Interpretation |
X1_success | -n1 | No failure | 0 | [["A"], ["B space"], ["C\nline"], ["-D"]] | ["A", "B space", "C\nline", "-D"] | Positive control: all four effects. |
X2_exit_one | -n1 | 1 to 1 | 123 | [["A"], ["B space"], ["C\nline"], ["-D"]] | ["A", "C\nline", "-D"] | REPAIR: later workers ran. |
X3_size_flag | -n1; -x | 1 to 1 | 123 | [["A"], ["B space"], ["C\nline"], ["-D"]] | ["A", "C\nline", "-D"] | REPAIR: size flag did not stop them. |
X4_exit_255 | -n1 | 255 to 255 | 124 | [["A"], ["B space"]] | ["A"] | Serial stop observed; A remains. |
X5_map_failure | -n1; map | 1 to 255 | 124 | [["A"], ["B space"]] | ["A"] | ACCEPT_BOUNDED scheduling; reconcile effects. |
X6_two_per_child | -n2 | 1 to 1 | 123 | [["A", "B space"], ["C\nline", "-D"]] | ["A", "C\nline", "-D"] | REPAIR: next group ran. |
X7_outer_errexit | -n1; Bash -e | 1 to 1 | 123 | [["A"], ["B space"], ["C\nline"], ["-D"]] | ["A", "C\nline", "-D"] | REPAIR: only the parent marker stopped. |
X8_empty_default | -n1; empty input | No failure | 0 | [[]] | [] | One zero-item invocation. |
X9_empty_no_run | -n1; -r; empty input | No failure | 0 | [] | [] | No worker invocation. |
Then inspect the event sequence behind each row. A failure should retain the correct identifier and original code. Every admitted successful item should have its expected invocation membership, start, and effect. Reject duplicates, unexplained events, reordered identities, invalid JSON, or a ledger that ends before the required checkpoint.
Counts alone cannot distinguish an expected identifier from a substituted one. Nor can a diagnostic line on stderr establish the full effect inventory. Preserve stderr as supporting evidence, but do not parse its wording into an application-state oracle. The exact executable names and message formatting belong to a particular run.
A missing or partial ledger forces HOLD unless the admitted case explicitly expects no invocation and the surrounding completed evidence supports that expectation. Never fill a gap by borrowing another case's result. The table is a comparison target, not permission to reconstruct observations that were never recorded.
Recover only the work whose disposition is known
Choose the recovery inventory from the actual case, not from the desired policy. After X5, A's synthetic effect is complete, B failed before its effect, and C and -D were not invoked. After X2, A, C, and -D have effects, while B failed before its own. Those two failed batches need different replay manifests.
A reader can derive a candidate recovery list from these laboratory facts, but this article does not execute a retry. Preserve the original attempt, identify which failure would be repaired, and obtain authorization for the proposed remaining work. A new attempt should produce new evidence rather than overwrite the prior ledger.
The laboratory's convenient boundary is explicit: B fails before the synthetic effect. A real worker may report failure after completing an effect or after reaching an outcome that its caller cannot determine. Before replay, reconcile that outcome through evidence appropriate to the application. The present logs do not prove external completion, safe repetition, or automatic undo.
That judgment belongs within operational ownership and maintenance, rather than being delegated to a scheduler's aggregate code. The recovery owner should identify known complete work, known failed-before-effect work, known unstarted work, and unknown dispositions separately. Unknown is not another spelling of unstarted.
Already-running parallel work is out of scope
The GNU parallelism guidance distinguishes serial execution from multiple concurrent commands. This playbook admits only -P1. A stop after one parallel child fails must not be described as cancellation of children already running. Their work and evidence require a different review.
Adding -P2, changing the worker to launch uncontrolled background activity, or introducing shared-state concurrency leaves the accepted model. Do not reuse the serial event order as an oracle for those systems. HOLD the claimed design until its own invocation, effect, and recovery contract is qualified. This boundary is about evidence and authority, not an assertion that concurrent automation is inherently unacceptable.
Set the acceptance gate and assign the batch owner
Separate qualification of the scheduling mechanism from approval of the application's outcome. X5 can earn ACCEPT_BOUNDED for its selected serial stop policy while still representing a failed operation that requires reconciliation. The gate should make both statements visible instead of collapsing them into a single green label.
1. Freeze the admitted configuration. Require the ordered authored IDs, GNU implementation and recorded version, unchanged worker digest, -0, -P1, grouping choice, and explicit empty-input policy. Compare the actual command vector, not a remembered command or an abbreviated console line.
2. Check the failure translation. For X5, require exactly the expected B failure with original code 1, the configured mapping to child 255, aggregate 124, and no conversion of that failed batch into caller-visible application success.
3. Compare the complete evidence. Require A's invocation, start, and effect followed by B's invocation, start, and failure, with no later item events. Match every case against its own matrix row. Extra events, wrong identities, missing required output, or an unreadable ledger fail the gate even when an aggregate matches.
4. Approve recovery separately. Record the known dispositions and the authorized next action. Reconcile existing effects before replay. HOLD unknown outcomes, unsupported worker faults, incomplete runs, or parallel execution presented under the serial contract.
The automation owner selects continuation versus stopping, argument grouping, and the scheduler-facing policy. The worker owner defines what failure and effect mean, including whether errors can occur after an effect. Operations authorizes recovery. QA retains the source, environment, independent expectations, and exact observed evidence.
Keep negative controls in that evidence package. X2, X3, X6, and X7 diagnostically pass when they reproduce the continuation that makes their candidate policy unacceptable. Removing those cases weakens the comparison. The discipline of repeatable test evidence is relevant here because the decision depends on identity, order, and explicit expected outcomes, not an attractive console summary.
Requalify changes to the implementation, worker codes, grouping, logging behavior, shell wrapper, or concurrency. For a real integration, add application-specific outcome checks and failure positions before claiming broader coverage. Those tests are proposed work, not observations from this synthetic run. Neither the local ledger nor its digest establishes durable exactly-once processing.
Practice controlled Linux automation through structured learning
A useful practice session ends with a decision that another operator can reproduce. The learner should be able to explain why X2 reports failure yet continues, why X3's size flag is irrelevant to that ordinary failure, and why X7 stops a parent statement without stopping xargs's later children.
The repair lesson is equally specific: X5 retains B's original failure, exposes child 255, receives aggregate 124, and records no later serial invocation. A remains. That is an accepted scheduling boundary for this fixture, not a successful application operation or permission to repeat the whole manifest.
Build the learning exercise around ownership as well as syntax. Have one person state the intended policy, another inspect the exact ledger, and the recovery owner explain which identifiers are complete, failed before an effect, or unstarted. Changing only one condition at a time makes disagreements about the contract easier to identify.
For broader foundations, explore Refonte Learning's System Administration Program, whose published scope includes Windows and Linux systems, command-line administration, networking, and troubleshooting. Those foundations support controlled automation practice; this specialist xargs fixture is not presented as a promised programme module.
