-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change heroku run -x
implementation to use a Bash EXIT trap
#2468
base: main
Are you sure you want to change the base?
Conversation
Appending our own `; echo …` to the command has several drawbacks: - it does not run if the command contains an `exit` statement (simple example: `heroku run -x 'exit 3'`); - it does not run if the shell itself exits, e.g. on commands that use `set -e` or `set -u`; - it breaks when the preceding command uses valid syntax that conflicts with the suffix (simple example: `heroku run -x 'echo hello;'`); - it causes the wrong exit status to be reported in `heroku logs` - it's always 0 (from the `echo`), not the correct value. Using a `trap` on `EXIT` instead works around all of these issues.
The match call uses multiline mode for no reason (it contains no anchors). The replace call anchors at the start of the string, but this is not a guarantee, and on failure, the magic string is visible in the output (simple example: `heroku run -x 'echo "hi"; echo -n "yo"'`). Solution is to remove the leading anchor. It also leaves behind a stray newline after replacing - the reason is that the `\n?` is ungreedy due to the `?`, so it will not actually match the newline character. The result is that using `heroku run -x` produces an extra blank line at the end of the output. Solution is to not have the newline optional, or to use a different trailing marker - I have re-used `\uFFFF` here, because that also allows removing multiline mode. Ideally, we'd use a proper unique string - e.g. generate a GUID for the command, and match that exact GUID later. Could also use a different separator - `\uFFFF` is not legal unicode, but 💜 is ;) (that's `\U1F49C` in Bash, with uppercase "`U`", and `\uD83D\uDC9C` in JavaScript, expressed as a UTF-16 surrogate pair).
Just fwiw, don't immediately have to merge this; we can brainstorm the best approach for the magic string delimiter etc first. Feel free to leave open for a while. |
Just stopping by to say that this would probably solve a weird bug that may be incidental to |
I don't think that's related, per se, @lake-effect. The difference between |
Background
When
-x
is passed toheroku run
, the given command is suffixed with a specialecho
that returns$?
, the exit status of the program:cli/packages/run-v5/lib/dyno.js
Line 54 in b451c6b
This
\uFFFF heroku-command-exit-status: …
output is later detected and used:cli/packages/run-v5/lib/dyno.js
Lines 315 to 328 in b451c6b
Exit status reporting issues
Appending our own
; echo …
to the command causes several bugs:exit
statement (simple example:heroku run -x 'exit 3'
):bash -c
instead, as a workaround:set -e
orset -u
:heroku run -x 'echo hello;'
):heroku logs
- it's always 0 (from theecho
), not the correct value:Exit status reporting solution
Using a
trap
onEXIT
instead works around all of these issues (in this example, the construction is a bit convoluted due to escaping challenges, but the principle is the same as the contents of this PR):Magic string handling issues
Additionally, there are problems with the matching and removal of the magic
\uFFFF heroku-command-exit-status: …
string.First, the magic string replace anchors on the beginning of a line, but if the last line of output does not end with a newline, this will fail:
Second, it always leaves behind a newline character. This means the output between
heroku run
andheroku run -x
differs:The reason is that the
\n?
is ungreedy due to the?
, so it will not actually match the newline character.Magic string handling solution
The anchoring at the beginning of the line is easy to remove in the
replace
Regex.For the trailing newline, the solution is to not have the newline optional in the Regex (our
echo
always prints it, after all), or to use a different trailing marker - I have re-used\uFFFF
here, because that also allows removing multiline mode.Ideally, we'd use a proper unique string - e.g. generate a GUID for the command, and match that exact GUID later. Could also use a different separator -
\uFFFF
is not legal unicode, but 💜 is ;) (that's\U1F49C
in Bash, with uppercase "U
", and\uD83D\uDC9C
in JavaScript, expressed as a UTF-16 surrogate pair).