TL;DR: The Quick Fix
Robocopy doesn't play by the usual rules. While most tools use 0 for success and anything else for failure, Robocopy returns 1 when it successfully copies files. Most CI/CD platforms like GitHub Actions or Azure DevOps see that 1 and immediately kill your build.
To fix this, you need to intercept the exit code. If the value is less than 8, tell the pipeline to treat it as a success:
robocopy source destination /options
if %ERRORLEVEL% LEQ 1 exit 0
Why your pipeline is failing
Standard CLI tools are binary: 0 means it worked, and 1 means it broke. CI/CD runners are hardwired to stop the build the moment they see a non-zero exit code. Robocopy, however, was designed for detailed logging using a bitmask system. It uses exit codes to tell you exactly what happened during the sync.
Here is how Robocopy breaks down its return values:
- 0: No files were copied. The source and destination were already identical.
- 1: Success. One or more files were successfully moved to the destination.
- 2: Extra files exist in the destination that aren't in the source.
- 4: Mismatched files or directories were detected.
- 8: Failure. Several files could not be copied.
- 16: Fatal error. This usually means a disk is full, access is denied, or the network path vanished.
Because 1 is greater than 0, your runner sees ROBOCOPY : Exit : 1 (0x01) and assumes the worst. It flags a failure even though your 500MB of production assets just finished uploading perfectly.
Fixing the Exit Code Logic
Method 1: Batch Script Wrapper
Batch scripts are the most common way to trigger Robocopy on Windows runners. If you are using a .bat or .cmd file, add a check immediately after your command. This ensures the script returns a clean 0 to the runner.
robocopy "C:\build\artifacts" "\\deploy\server" /E /Z /R:5 /W:5
:: If the code is between 0 and 7, force it to 0 so the build passes
if %ERRORLEVEL% LSS 8 exit /b 0
Method 2: PowerShell Handling
PowerShell is the default shell for many modern Windows runners. It tracks the last exit code in the $LASTEXITCODE variable. You can reset this value manually to keep the pipeline moving.
robocopy "source" "destination" /MIR
if ($LASTEXITCODE -lt 8) {
$global:LASTEXITCODE = 0
}
This approach is safer for automated deployments. It allows for minor warnings (like exit code 2 for extra files) while still failing if a real error (code 8 or 16) occurs.
Method 3: GitHub Actions YAML Fix
When calling Robocopy directly inside your main.yml, you can chain the commands. This is the cleanest way to handle the logic without creating separate script files.
- name: Deploy via Robocopy
shell: cmd
run: |
robocopy "src" "dest" /E /NP
if %ERRORLEVEL% LEQ 1 exit 0
How to verify the fix
Check your pipeline logs after applying these changes. You should still see ROBOCOPY : Exit : 1 in the text output, but the step itself will now show a green checkmark.
- Trigger a manual build.
- Locate the Robocopy task in the logs.
- Confirm the pipeline moves to the next step instead of hanging.
- Spot-check the destination folder to ensure your files—whether it's 10 images or 1,000 DLLs—actually arrived.
Which codes actually matter?
Don't ignore every error. If Robocopy returns 8 or 16, your pipeline should fail. These codes indicate serious issues like Access Denied or Insufficient Disk Space. By using the LSS 8 (Less Than 8) logic, you ignore the helpful "files copied" notices while still catching the errors that would actually break your deployment.

