Once INT 21h AH=4Bh has returned, the parent asks with 4Dh how the child went. AL is the code the child passed in AL to INT 21h AH=4Ch (0 if it ended with INT 20h or a RET), and AH says how it ended:

AHThe child ended
0normally: 4Ch, INT 20h, a RET to its PSP
1with Ctrl+C or Ctrl+Break (INT 23h)
2through a critical error: Abort at the Abort, Retry, Fail prompt (INT 24h)
3with INT 21h AH=31h: it stayed resident

The shell does the same after every program it runs and keeps AL as ERRORLEVEL for batch files. IF ERRORLEVEL 3 is true when the code is 3 or more, so batch files test the highest codes first.

In MS-DOS the code can be read only once: DOS clears it on reading, and a second 4Dh returns 0. DOSBox-X keeps it and returns the same code again. Either way, read it right after the EXEC.

The example writes itself a child: a five-byte TEST.COM, B8 07 4C CD 21, which is MOV AX, 4C07h / INT 21h. It runs it, reads the code, and deletes the file:

prints: the child returned AX = 0007
MOV AH, 4Ah ; make room for a child
MOV BX, 1000h
INT 21h
MOV AH, 3Ch ; DOS: create TEST.COM
XOR CX, CX ; a normal file
MOV DX, OFFSET PROG1
INT 21h
JC DONE1
MOV BX, AX ; the handle
MOV AH, 40h ; DOS: write the child, five bytes
MOV CX, 5
MOV DX, OFFSET CHILD1
INT 21h
MOV AH, 3Eh ; DOS: close it
INT 21h
MOV TAILP1+2, CS
MOV FCB1P+2, CS
MOV FCB2P+2, CS
MOV SAVESP1, SP
MOV AX, 4B00h ; DOS: run it
MOV DX, OFFSET PROG1
MOV BX, OFFSET ENV1 ; ES:BX = the parameter block
INT 21h
MOV BX, CS ; MOV leaves CF alone: segments back first
MOV SS, BX
MOV SP, CS:SAVESP1
MOV DS, BX
MOV ES, BX
JC GONE1 ; it did not run
MOV DX, OFFSET CODEM1
MOV AH, 09h
INT 21h
MOV AH, 4Dh ; DOS: how did the child end?
INT 21h ; AL = its code, AH = 0: through 4Ch
CALL PHEX
MOV DX, OFFSET CRLF
MOV AH, 09h
INT 21h
GONE1:
MOV AH, 41h ; DOS: delete TEST.COM
MOV DX, OFFSET PROG1
INT 21h
DONE1:
PROG1 DB "TEST.COM", 0
CHILD1 DB 0B8h, 07h, 4Ch, 0CDh, 21h ; MOV AX, 4C07h / INT 21h
TAIL1 DB 0, 13 ; an empty command tail
ENV1 DW 0 ; parameter block: environment, 0 = a copy of ours
TAILP1 DW OFFSET TAIL1, 0 ; the command tail, offset and segment
FCB1P DW 5Ch, 0 ; FCB 1: the one in our PSP
FCB2P DW 6Ch, 0 ; FCB 2
SAVESP1 DW 0
CODEM1 DB "the child returned AX = $"
PHEX PROC NEAR ; print AX as four hex digits (changes AX BX CX DX)
MOV BX, AX
MOV CX, 4
PHEX1:
PUSH CX
MOV CL, 4
ROL BX, CL ; the top digit to the bottom
POP CX
MOV DL, BL
AND DL, 0Fh
ADD DL, "0"
CMP DL, "9"
JBE PHEX2
ADD DL, 7 ; "A" to "F"
PHEX2:
MOV AH, 02h
INT 21h
LOOP PHEX1
RET
PHEX ENDP
CRLF DB 13, 10, "$"