Loosely Decoupled
TroubleshootingBatchAutomation

Why Does My Batch File Exit When I Call a Function?

The subroutine runs before anything calls it, and its goto :eof ends the whole script, because cmd.exe reads straight down through a label.

A relaxed developer with a coffee strolls straight through a side room towards an exit door. Behind a glass wall, the real workroom with its glowing cyan lamp sits empty and unvisited.
On this page

TL;DR

A batch label does not create a function body that cmd.exe skips. Execution runs straight through the label unless you redirect it.

Put the main work first, end it with exit /b, and place subroutines below it:

BATCH
@echo off
setlocal EnableExtensions
call :timestamp
echo [%stamp%] Work starts here
exit /b 0

:timestamp
set "stamp=%DATE% %TIME%"
goto :eof

call :timestamp creates a return point. goto :eof returns from that call. Reached in the main batch context, it ends the batch instead.

You add a timestamp helper to a working batch file. The helper looks reasonable. The next run prints almost nothing, and the real work never starts.

It’s tempting to debug the timestamp expression. First check whether the script ever reaches the line that calls it.

Minimal reproduction: the call never happens

Save this as fall-through.cmd:

fall-through.cmd
@echo off
echo Starting script

:timestamp
echo Inside timestamp
set "stamp=%DATE% %TIME%"
goto :eof

call :timestamp
echo [%stamp%] Doing the work

Run it from an existing Command Prompt so the output stays visible:

Expected output

C:\scripts> cmd /d /e:on /c fall-through.cmd
Starting script
Inside timestamp

C:\scripts>

That output is the clue: the helper ran even though the call below it was never reached. Execution arrived there by reading downward.

A label marks a location. It doesn’t surround or hide the commands beneath it. The goto :eof then ends the current batch context, so both remaining lines are unreachable on this path. Microsoft’s goto reference (opens in a new tab) documents the special end-of-file target; you don’t need to define an :eof label.

End the main path with exit /b before the subroutines

Here is the complete repaired example:

no-fall-through.cmd
@echo off
setlocal EnableExtensions

echo Starting script
call :timestamp
echo [%stamp%] Doing the work
exit /b 0

:timestamp
echo Inside timestamp
set "stamp=%DATE% %TIME%"
goto :eof

Expected output, with the timestamp varying by locale and clock:

Text
Starting script
Inside timestamp
[<local date> <local time>] Doing the work

The main path calls the helper, resumes after the call, does its work, and exits before reaching the helper a second time. The helper is now reachable only through an intentional call.

Moving a subroutine to the bottom without the main-path exit is incomplete. The call returns correctly, but after the main work finishes, execution falls into the helper again.

What call, goto and exit /b each do

call :label starts a new batch context within the same file. When that context ends, execution resumes at the statement after the call. goto label only jumps; it doesn’t arrange a return. See Microsoft’s call reference (opens in a new tab).

CommandEffect
call :timestampEnter the subroutine with a return point
goto timestampJump to the label without a return point
goto :eofEnd the current batch context
exit /b 0End the current batch context and set a success status
exitExit the command interpreter

Warning

Plain exit inside a batch file exits the command interpreter, not only the script. Run it from a Command Prompt you were using and that window closes, taking any unsaved session state with it. exit /b is almost always what you want.

The /b matters. exit /b ends the current batch context instead of cmd.exe, as Microsoft’s exit reference (opens in a new tab) says. A call :label runs in its own context, so inside a subroutine exit /b returns to the caller, and at the top level it ends the file. Use an explicit code when the caller needs a result.

These label-call and :eof examples assume command extensions are enabled, as they normally are. The repaired file makes that requirement explicit with setlocal EnableExtensions.

Helpers can also go above main

There’s no rule that subroutines must live at the bottom. If you prefer helpers first, jump over them on entry:

BATCH
@echo off
setlocal EnableExtensions
goto main

:timestamp
set "stamp=%DATE% %TIME%"
goto :eof

:main
call :timestamp
echo [%stamp%] Doing the work
exit /b 0

Placement is a readability choice. An unambiguous execution path is the fix.

Common failures

The window disappears immediately. Run the file from an existing Command Prompt. A window opened only to run a batch file can close when that file ends; the disappearance alone doesn’t tell you which command caused it.

The helper runs twice. Look for a missing exit /b or goto :eof between the end of the main work and the first helper label.

The main work never resumes. Check for goto timestamp where you intended call :timestamp, or plain exit inside the helper.

I only needed a timestamp in a log. A separate subroutine may be unnecessary:

BATCH
echo [%DATE% %TIME%] Backup started>>"backup.log"

This is a display timestamp, not a locale-independent filename format. Keep this simple example outside a parenthesized block, where percent-variable expansion introduces a separate timing issue.

When a batch file stops early, follow the lines it actually executes. A label that looks like a function declaration is still on that path.

Tested with

OS
Windows Server 2019 (10.0.17763)
Shell
cmd.exe
Date
October 2026

New articles, by email.

A short note when something new is published.