When using compiled escripts to share Erlang programs the program author and the program user need to have compatible versions of Erlang and the BEAM installed. If one was using OTP26 and one was using OTP29 the escript would likely fail to run and show a cryptic error message, confusing the user.
I’ve been trying to work out a way to have the escript start by checking the running VM version, printing a helpful message if it is likely not compatible, as I think this would greatly help with the user-experience of escript programs.
My thought was that perhaps the escript zip archive could contain a single not-yet-compiled Erlang source module, and have that do the checking before running the already-compiled code, but I’ve not discovered a way to do that from reading the source of escript.erl.
Is there a way to embed forward-and-backwards compatible code within a compiled escript?
Alternatively, is there some other way to improve the error message? To make it more understandable to users without this Erlang knowledge.
Aye! Getting metadata into the script is easy, but I was unable to work out how I could use said metadata as the precompiled .beam files may not be executable. How would you use that metadata?
Would it be possible to mix compiler versions within the escript? My first thought is to have a stub that’s compiled with a minimum version (OTP-26) that runs whatever checks you need before calling the real entry point that’s compiled with OTP-29.
I might be mistaken, but I don’t believe the bytecode is not forwards compatible, so older versions may not work. That or my older escripts failed for some other reason!
My thought was to update the escript executable to check this metadata. Sounds like you’re after something you as the builder of an escript could do w/o modifying escript. Off the top of my head you would either need to not compile the modules, or wrap it in a shell script that does the version check before either running the escript or printing the user-friendly message you’re after.
As long as the BEAM doesn’t guarantee that I’ll run arbitrarily old .beam files, there is not going to be any form of “universal” BEAM object files or executables (compiled escripts).
Compiled code can be loaded on at least two subsequent releases.
So you’ve got 27, 28 and 29 (but not 26).
I also wondered about using a textual escript to bootstrap the rest of the script, but you’d need to embed the ZIP file at the end, which is gonna confuse the loader. You’d need to extract it as part of the bootstrapper. See, for example, GitHub - rlipscombe/escriptize: Replacement for `rebar escriptize` · GitHub, which I wrote 11 years ago to extract NIFs to the filesystem before running an escript. That still uses a compiled beam as the bootstrap, though, so it’s not directly comparable.
Yeah; this is the obvious way to do it, but it’s not as slick as having everything in one file. I think there are ways to do self-extracting bash scripts, but you’re still having to do work to keep the ERTS loader happy.
Sounds like there is no way to do this, alas. I’m trying to make the BEAM more viable for CLI tools in a style similar to JavaScript and Python, but currently I can’t find a way to dodge showing very cryptic errors to folks using different VM versions. If we could display an error message that is understandable without deep Erlang knowledge that would make escripts much more viable for wider usage.
Have you tried bundling the runtime in the escript? That should avoid the “user-unfriendly error message due to runtime version mismatch” issue. escript — erts v16.4
That would be great! I didn’t know that was possible outside of executable archives.
Though that link goes to the documentation to the escript functionality for using an OTP installation found at a path relative to the escript file, rather than documentation for bundling the runtime in an escript. Do you have the documentation for that?
Include the Erlang version used to build the escript as metadata in the zip file (for example, 26) and escript checks it is not 3+ versions in the past. You could also do that for BEAM files in the escript but it seems simpler to assume that the Erlang version for the escript and BEAM are the same.
Sorry, I think I’m being a bit slow. How would the escript do that checking?
If it’s out-of-date the BEAM files can’t be loaded and run, so the checking code couldn’t be pre-compiled to BEAM bytecode. I spent a while trying to construct an escript that had both BEAM files and a version-checking source Erlang file, and then to use the source Erlang files as the entrypoint, but I wasn’t able to get it to work. The source file seemed to always be ignored. I wasn’t able to find the relevant code in OTP’s escript system to confirm that it was impossible, so it may have been a mistake somewhere on my part.
How about something in escript_emu_args? I’m assuming that’s read before any BEAM file is loaded. Would it work to have the built-with version in there, and have escript check that?
I think Mikael and José are talking about having the actual escript executable (i.e. the one at /opt/homebrew/bin/escript or wherever) read the metadata and generate a sensible error. This would mean changes to Erlang/OTP itself, though, which might need to wait for the next major release (and three releases after that, to be effective).
Just to chime in. Adding the Erlang version during the escript build phase is probably the only real solution. The downside is it’d not be backward compatible.
Rather than add to the existing emu_args section, I’d suggest adding a new section to the escript header. They’re just injected as comment lines (with prefixes) when the escript is created and parsed out of the header comment when the escript starts.
Section
Prefix
shebang
#!
emu_args
%%!
comment
%%
Would be clean to just add a new section to the metadata, maybe otp_version with a new prefix.
The version checking during the start phase should be quite simple
Why would that be? In my test any additional files in the zip archive get ignored. The old BEAM wouldn’t be impacted, and the new BEAM could gracefully handle that data being absent.
This was an assumption based on earlier posts and “cryptic error messages”.
So I went to check for myself, and compiled escript using 27.x and tried to run using 23.x
=ERROR REPORT==== 13-May-2026::12:25:02.314867 ===
beam/beam_load.c(1879): Error loading module etest:
This BEAM file was compiled for a later version of the run-time system than 23.
To fix this, please recompile this module with an 23 compiler.
(Use of opcode 171; this emulator supports only up to 170.)
escript: exception error: undefined function etest:main/1
in function escript:run/2 (escript.erl, line 758)
in call from escript:start/1 (escript.erl, line 277)
in call from init:start_em/1
in call from init:do_boot/3
While not a friendly as You need to use at least Erlang X.XX to run this script I don’t think it’s that bad…