A program that gets a non-fatal I-O status, such as 22 (duplicate key) or 10 (end of file), is stopped by libcob when the file has no FILE STATUS clause and the statement has no INVALID KEY or AT END phrase. The standard says control should go to the end of the statement and the program should carry on. With a FILE STATUS clause the program already carries on, so the same code behaves differently depending on an unrelated clause.
ikey.cob (attached) writes the same key twice:
IDENTIFICATION DIVISION.
PROGRAM-ID. IKEY.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT F ASSIGN TO "ikey.dat" ORGANIZATION INDEXED
ACCESS RANDOM RECORD KEY F-KEY.
DATA DIVISION.
FILE SECTION.
FD F.
01 F-REC.
05 F-KEY PIC X.
PROCEDURE DIVISION.
OPEN OUTPUT F
MOVE "A" TO F-KEY
WRITE F-REC
WRITE F-REC
DISPLAY "AFTER WRITE"
CLOSE F
STOP RUN.
Two more programs are attached. ikeyfs.cob is the same with FILE STATUS FS added to the SELECT and 01 FS PIC XX in WORKING-STORAGE, and it displays "AFTER WRITE " FS. atend.cob opens an empty sequential file F for input and does READ F with no AT END phrase, then DISPLAY "AFTER READ".
cobc -std=cobol2014 -fformat=free -x ikey.cob && ./ikey
Each run starts with no .dat file.
Every compile exits 0 with no cobc diagnostic.
WRITE with a duplicate key, no INVALID KEY phrase, no FILE STATUS. 3.2.0, run exit 1:
libcob: error: record key already exists (status = 22) for file F ('ikey_dat' => ikey.dat)
libcob: warning: implicit CLOSE of F ('ikey.dat')
AFTER WRITE is not displayed. 3.3-dev r5751, the same command: byte-identical output, run exit 1.
With the FILE STATUS clause (ikeyfs.cob, same command), both versions print AFTER WRITE 22 and exit 0.
READ at end, no AT END phrase, no FILE STATUS (cobc -std=cobol2014 -fformat=free -x atend.cob && ./atend). 3.2.0, run exit 1:
libcob: error: end of file (status = 10) for file F ('atend_dat' => atend.dat)
libcob: warning: implicit CLOSE of F ('atend.dat')
AFTER READ is not displayed. 3.3-dev r5751, the same command: byte-identical output, run exit 1.
ikey prints AFTER WRITE and atend prints AFTER READ, both exit 0, as ikeyfs already does. ISO/IEC 1989:2023 says:
The 2014 and 2002 editions have the same invalid key and AT END rules (in their numbering the invalid key rule is 9.1.13 rule 4 and the READ rule is 21 d)), and list only 3x, 4x and implementor-defined 9x as fatal.
If the current behaviour is wanted for compatibility with other compilers, a dialect option that is off for the ISO dialects would keep both.
The attached run-unit-ends-on-nonfatal-io-status.patch is against 3.3-dev r5751 and applies with patch -p1 at the top of the tree. It changes output_error_handler in cobc/codegen.c. In the default error handler, for a file without a FILE STATUS clause, cob_fatal_error (COB_FERROR_FILE) is now called only when the first digit of the I-O status is 3, 4, 7 or 9, the fatal classes of §9.1.13.1 (all 9x statuses stay fatal, as now). For any other status the handler returns and control goes to the end of the statement, as it already does when the file has a FILE STATUS clause. This also lets 5x and 6x continue, which §9.1.13.1 likewise does not list as fatal. Declaratives are unchanged: they run before this check, as before.
With the patch, the same commands as above (built-in-tree cobc and libcob), each run starting with no .dat file:
$ cobc -std=cobol2014 -fformat=free -x ikey.cob && ./ikey
AFTER WRITE
(exit 0)
$ cobc -std=cobol2014 -fformat=free -x ikeyfs.cob && ./ikeyfs
AFTER WRITE 22
(exit 0)
$ cobc -std=cobol2014 -fformat=free -x atend.cob && ./atend
AFTER READ
(exit 0)
ikey gives the same with the default dialect. A fatal status still ends the run: a READ of a file that was never opened (status 47, no FILE STATUS) still prints READ/START not allowed, file not open for input (status = 47) and exits 1.
make check on 3.3-dev r5751 with the patch: 1,373 behaved as expected, 9 skipped, 0 failed, as unpatched (1,372 + the new test); the new test 921 "nonfatal I-O status without FILE STATUS" (tests/testsuite.src/run_file.at) passes. It covers status 10 on a READ and 22 on a WRITE to a RELATIVE file, and checks that status 47 still ends the run.
The patch also updates the expected output of the existing test "TURN EC-I-O" (run_file.at), which pinned the old behaviour: its last READ of file g (no FILE STATUS) at end of file stopped the run with end of file (status = 10) ... on READ. It now expects that READ to continue and the program to exit 0. That test is marked AT_XFAIL_IF([true]) for an unrelated FIXME (EXCEPTION-STATUS after >>TURN ... OFF); with the patch, the FIXME lines are its only remaining difference.
GnuCOBOL 3.2.0 (Ubuntu gnucobol3 3.2-4build1) and 3.3-dev r5751 (CI snapshot 3.3-dev.138 of 2026-10-29), x86_64 Linux, both re-run 2026-10-30. On 3.2.0 the Ubuntu C compiler step also prints <command-line>: warning: ‘_FORTIFY_SOURCE’ redefined and its note; that is the distribution's build setting.