Scientific Computing

CTest interactive debug mode

CTest –interactive-debug-mode is particularly useful on Windows to enable/disable debug dumps and error popup modal windows, especially for missing DLL errors. By default this feature is enabled. This default behavior to have interactive debug mode “on” can make problems with modal popups hanging on automated systems like CI or agentic workflows where user interaction is not desired or possible.

To avoid this, one can explicitly disable interactive debug mode by option

ctest --interactive-debug-mode=0

An even better approach is to make CMakePresets.json explicitly set the interactive debug mode for CTest invocation with preset or workflow with "interactiveDebugging": false thats inherited for all CTest preset execution like:

{
  "version": 6,

"testPresets": [
{
  "name": "default",
  "configurePreset": "default",
  "output": {
    "outputOnFailure": true,
    "verbosity": "verbose"
  },
  "execution": {
    "interactiveDebugging": false,
    "noTestsAction": "error",
    "scheduleRandom": true,
    "stopOnFailure": false
  }
}
]
}

In agentic instruction files like CMAKE.instructions.md or similar, consider adding a line like:

CTest on Windows should be run with option --interactive-debug-mode=0 to prevent modal popups from interrupting automated workflows.

NOTE: CTest environment variable CTEST_INTERACTIVE_DEBUG_MODE is read-only and has no effect on setting the interactive debug mode. The mode must be controlled via the --interactive-debug-mode command-line option when invoking CTest.


Related: MSVC exception handling popup disable

Disable Visual Studio exception modal window

Visual Studio executables built in Debug mode by default pop up modal debug windows if an unhandled exception occurs. This can be can be undesirable for reasons including:

  • users having to close nuisance windows manually.
  • CI runs hang if an exception occurs and the modal window appears.
  • AI agents or automated tools may hang with unexpected modal windows.
  • remotely operated systems: modal windows can be difficult to close if the modal window is accidentally off-screen.

Debug popups for exception handling can be disabled by C++ runtime library functions provided by Visual Studio described below. NOTE: missing DLL modal popups are fixed by the calling program such as CTest instead of this C++ method below.

Adding a few lines of code to the C++ program works around this issue by redirecting the error text to stderr console and not popping up the modal window.

This is also relevant to continuous integration systems such as GitHub Actions, which may hang with an unrealized modal dialog otherwise.

#ifdef _MSC_VER
#include <crtdbg.h>
#endif

int main(){
#ifdef _MSC_VER
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
#endif

// rest of program
}

CMake version recommendations and install

Downloading the latest release of CMake is usually easy. Admin / sudo is not required.

  • Linux: snap install cmake
  • macOS: brew install cmake
  • Windows: winget install Kitware.CMake
  • PyPI CMake package: python -m pip install cmake

For platforms where CMake binaries aren’t easily available, build CMake using scripts/build_cmake.cmake.

To see the merge requests for a certain release, use a URL like: https://gitlab.kitware.com/cmake/cmake/-/merge_requests?milestone_title=4.3.0&scope=all&state=all

Another CMake changelog summary we like.

CMake 4.5 additions include:

CMake 4.4 additions include:

CMake 4.3 additions include:

  • Common Package Specification (CPS) officially supported. This provides a solution for the long-standing program of build system-agnostic JSON package configuration files, which can be generated by CMake’s install(EXPORT) and consumable by other build systems.
  • cmake --version=json-v1 tells details of the libraries vendored into CMake, which is useful for debugging and reporting issues with CMake’s bundled dependencies. This is complementary to cmake -E capabilities which reports specific capabilities of the CMake.
  • CMake command line and file(ARCHIVE_CREATE) commands can specify compression level and algorithm in more detail
  • numerous generator string expressions were added

CMake 4.2 additions include:

  • Visual Studio 18 2026 generator added
  • cmake_language(TRACE) enables tracing just selected nestable portions of CMake script, which is important for debugging CMake projects due to the large volume of trace output.
  • Ability to force shorter paths on project builds, important for large or complex project on Windows where the 260 character path limit is a problem for some tools. This is done via CMAKE_INTERMEDIATE_DIR_STRATEGY which is a CMake environment variable as well as a CMake command-line option.
  • FASTBuild generator has impressive benchmarks. FASTBuild supports more modern platforms including Windows, versus ccache or distcc.
  • ExternalProject added options to modify environment variables at each step by CONFIGURE_ENVIRONMENT_MODIFICATION, BUILD_ENVIRONMENT_MODIFICATION, INSTALL_ENVIRONMENT_MODIFICATION, TEST_ENVIRONMENT_MODIFICATION
  • Target property SPDX_LICENSE

CMake 4.1 additions include:

  • project() added COMPAT_VERSION that propagates to subdirectories and can be queried for the top-level COMPAT_VERSION.

CMake 4.0 additions include:


Older CMake release changelogs:

Detect CMake non-target directives

CMake directives can be target-specific - generally starting with target*() like target_compile_options() versus non-target-specific like add_compile_options() or include_directories(). It’s usually recommended to use target-specific directives whenever possible, as they provide better scope control over individual targets, avoiding applying directives in unexpected scopes.

CMake provides a warning flag -Wnon-target-directive to detect such directives. Sometimes they may be truly unavoidable or in a third-party library out of one’s scope.

cmake -Werror=non-target-directive -B build

or to only warn without treating it as an error:

cmake -Wnon-target-directive -B build

Terminal escape inter-program communications

Terminal escape sequences are often used to control the appearance and behavior of terminal emulators. Beyond visual effects, they can also facilitate inter-program communications by signaling events or passing information between programs running in the terminal. The OSC 7501 protocol of escape sequences allows programs to communicate their status to the terminal, which can then relay this information to other programs or scripts that are monitoring the terminal output. A prime use of this is for AI agents, but could also be used for coordinating other types of programs, such as build systems, monitoring tools, or custom terminal-based workflows. Check out the author’s article linked above for a detailed explanation and examples of how OSC 7501 can be used for inter-program communications in terminal environments.

Get directory of Bash script

Most shells don’t have a straightforward way to get the directory of the script being executed. There are one-line commands to get the script directory in each shell:

Shell one-line command
Bash sdir=$(dirname $(realpath ${BASH_SOURCE[0]}))
Zsh sdir=${${(%):-%x}:A:h}

Execution of scripts without a shebang goes to the system “/bin/sh”, which may be in POSIX mode and so may not support Bash- or Zsh-specific features.

With HPC batch systems, the above likely does not work as intended since the HPC batch scheduler generally copies the batch script to a temporary directory. Each HPC batch system has a way of indicating from where the batch script was originally submitted - which can be distinct from the script’s original directory. The technique is to be mindful of what directory one is submitting the HPC batch job from.

Batch system environment variable indicating submission directory
Slurm SLURM_SUBMIT_DIR
PBS PBS_O_WORKDIR
LSF LS_SUBCWD

Using “~” or “$HOME” within the batch script may not point to the user home directory due to the possibility of LSF account mapping, a different automount prefix, or a container environment. The HPC generally has a networked drive available with environment variables pointing to the intended directories for data and working storage. Use the documentation for the intended HPC batch system to determine the appropriate environment variables for data, scratch, and working directories. These environment variables would be used inside the HPC batch script.

Pull request scheduling AI skill

William Martin of GitHub has written an agent skill that pays down technical debt in GitHub’s command line interface cli/cli by opening a managed stream of pull requests. The skill and William’s commentary are in pull request 14095, merged in August 2026. The interesting parts are the set of constraints that keep the loop from outrunning the human who has to read the diff.

Martin aims to clear a backlog of roughly 1900 findings. A single cleanup pull request would be unreviewable. The skill is the alternative: one verified gap per run, opened ready for review, and then the run stops. The goal is a change so small that reviewing it takes about two minutes.

A stalled review halts production rather than letting work pile up behind it, so tick frequency does not matter: merging one a day yields one a day. An unattended run may try up to three targets. The first attempt that validates becomes the pull request. Failure notes from the others ride along so the next run does not rediscover them. A run that lands nothing is silent - the absence of a pull request is the signal. A human handles review comments, and the one-open-pull-request cap means the loop waits.

Steering goes through a committed file .experiments/tech-debt-burndown/memory.md. A human owns the Current focus section. The run may append rejected targets and failed attempts below it with a 150-line budget, and must re-check any factual claim it finds there. The pull request is opened ready for human review.

Pip install says Microsoft Visual C++ is required

On Windows if installing a Python package that requires a native build the following error may result if Visual Studio Build Tools isn’t installed:

error Microsoft Visual C++ is required

Check PyPI for a prebuilt wheel compatible with your Python version and Windows architecture to avoid the need for a local build.

Otherwise, it will be necessary to build the package from source by installing Microsoft’s Build Tools for Visual Studio. In the installer, select Desktop development with C++ and include the MSVC C++ compiler and Windows SDK components for the target architecture.

Use a toolset supported by the targeted Python release and package; match the compiler architecture to the Python interpreter. Check the Python interpreter architecture like:

#!/usr/bin/env python3
import struct
import platform

print(f"Architecture: {platform.machine()}")
print(f"Pointer size: {struct.calcsize('P') * 8} bits")

Python documentation for Windows extension builds and the package’s own build instructions are the references for version-specific requirements.

C or C++ extension modules are defined in pyproject.toml by the project.

Environment variables maximum size

Unix-like shells and Windows both limit how much data a new process can receive. Exceeding the limit produces Argument list too long (E2BIG) on POSIX, or a truncated / rejected command line on Windows.

ARG_MAX is the combined size of the argument vector and the environment block that exec / CreateProcess will accept.

getconf ARG_MAX

or from Python:

import os
print(os.sysconf("SC_ARG_MAX"))

POSIX defines ARG_MAX as the maximum length of the arguments to the exec family, including environment data. That means:

  • every argv[i] string (plus its terminating NUL)
  • every NAME=value environment string (plus NUL)
  • implementation-specific accounting for pointer arrays, null terminators, and alignment

On Linux, the executable path is also included in the E2BIG check.

On Linux, GNU xargs reports how much of the ARG_MAX budget the current environment already uses:

true | xargs --show-limits

POSIX’s guaranteed minimum is only _POSIX_ARG_MAX = 4096 bytes, which could be a consideration on micro embedded systems. Real systems ARG_MAX values are usually much larger on modern systems. POSIX does not define a portable way to calculate the exact remaining room because implementations may account for pointers, null terminators, and alignment differently.

Linux

Two independent limits apply.

ARG_MAX: overall combined size of the argument vector and environment block.

  • at most 1/4 of the current soft RLIMIT_STACK
  • at most 3/4 of the kernel’s _STK_LIM (8 MiB) → 6 MiB ceiling
  • at least 32 pages (128 KiB floor, since Linux 2.6.25)

Default stack is 8 MiB, so getconf ARG_MAX is typically 2097152 (2 MiB). Raising ulimit -s raises the reported value up to the 6 MiB cap; dropping the stack limit drops ARG_MAX toward 128 KiB.

<linux/limits.h> ARG_MAX header value is a compile-time minimum, not the runtime limit. Instead, use getconf or sysconf(_SC_ARG_MAX).

MAX_ARG_STRLEN: This is the limit constraining export of a huge variable.

A single argument or environment variable (NAME=value\0) cannot exceed MAX_ARG_STRLEN. Exceeding MAX_ARG_STRLEN results in execve returning E2BIG, even when the total is well under ARG_MAX. Raising MAX_ARG_STRLEN requires rebuilding the kernel.

macOS

Darwin does not have Linux’s per-string MAX_ARG_STRLEN cap. One argument or one environment variable can consume almost the entire ARG_MAX.

Typical values:

source typical value
getconf ARG_MAX 1048576 (1MiB)
sysctl kern.argmax 1048576 (1MiB)

kern.argmax is not tunable at runtime.

The budget still includes the environment. A 128 KiB PATH can contribute to exec failures when combined with the rest of the environment and the command arguments, even though 1 MiB looks ample on paper.

Shell builtins (echo, printf) are not subject to ARG_MAX. External binaries are.

Windows

Windows splits the problem across several APIs. There is no getconf ARG_MAX.

Layer Limit
CreateProcessW command line 32,767 characters including the terminating NUL
CreateProcessA environment block 32,767 characters
Unicode environment block (Vista+) no documented hard cap; 256 KiB+ works from Win32
Documented max one user-defined env var 32,767 characters
cmd.exe command line 8191 characters
cmd.exe inherited env var it will honor 8191 characters
set in cmd.exe 8191 characters
setx 1024 characters when assigning a value

So:

  • A program calling CreateProcessW can pass a ~32k character command line and a large Unicode environment.
  • Anything that goes through cmd.exe or a .bat file dies at 8191.
  • PowerShell and the Win32 APIs can set a single variable closer to the 32,767 character documented max; cmd cannot.

The environment block format is:

NAME=value\0NAME=value\0\0

Names cannot contain =.

How the two limits interact

Setting a huge variable in the current shell often succeeds. The failure happens at the next exec:

# Linux: this assignment may work in bash
export HUGE=$(python3 -c "print('x'*200000)")
# this exec fails with E2BIG because 200000 > MAX_ARG_STRLEN
/bin/true

On macOS the same pattern fails when len("HUGE=")+200000 plus the rest of the environment exceeds ARG_MAX.

On Windows, cmd.exe will not even accept a set line longer than 8191 characters, while CreateProcessW with an explicit environment block can go much further.

Prove the Linux per-string cap

import os, subprocess

for n in (131069, 131070, 131071):
    env = os.environ.copy()
    env["H"] = "x" * n
    try:
        subprocess.check_call(["/bin/true"], env=env)
        print(n, "ok")
    except OSError as e:
        print(n, e)

On a 4 KiB page Linux kernel, 131069 typically succeeds and 131070 fails because H= plus the value and the terminating NUL must fit within MAX_ARG_STRLEN.

Workarounds

  • Do not put large payloads in the environment or argv. Use a file, pipe, socket, or response file (@args.txt).
  • xargs / find -exec … {} + chunk argv to stay under the total.
  • On Linux, ulimit -s only helps the total, not MAX_ARG_STRLEN.
  • On Windows, call CreateProcessW directly (or PowerShell / Python subprocess with env=) instead of going through cmd.exe.
  • Keep PATH short; it is the environment variable that most often eats the budget.

Summary

Platform Total argv + env One environment variable
Linux (≥ 2.6.25) ~1/4 of stack, 128 KiB–6 MiB (often 2 MiB) 128 KiB (MAX_ARG_STRLEN)
macOS 1 MiB (kern.argmax) same as total
Windows Win32 32,767-character command line; large Unicode environment block 32,767 characters
Windows cmd.exe 8191 characters 8191 characters

The 128 KiB figure often quoted for Unix is the Linux per-string cap (MAX_ARG_STRLEN), not the total, and not a universal Unix number.

Run macOS Terminal command with Rosetta

On macOS, Terminal commands can be specified to run as a Rosetta CPU arch translated binary using the arch command. If supported by the macOS version, for Apple Silicon CPUs Rosetta can be installed with the following command:

softwareupdate --install-rosetta

Then run an x86_64 command like:

arch -x86_64 <command> <command options>

To run cmake as an x86_64 binary on an Apple Silicon Mac:

arch -x86_64 cmake -B build

Check the architecture of the current shell process:

arch -x86_64 uname -m

Universal or x86_64 binaries required

When using

arch -x86_64 <command>

to run a program, the program must be a universal binary or an x86_64 binary. If not, the error message will be similar to:

arch: posix_spawnp: <command>: Bad CPU type in executable

For programs like CMake that are designed for cross-compiling, command CMake to build for x86_64 by setting the CMAKE_OSX_ARCHITECTURES variable to x86_64:

cmake -B build -DCMAKE_OSX_ARCHITECTURES=x86_64

cmake --build build