Conversation
joe4dev
force-pushed
the
devx-1004-distinguish-proxied-tool-exits-from-lstk-errors-in-command
branch
3 times, most recently
from
September 10, 2026 13:00
e2f5cec to
8a19bc0
Compare
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
joe4dev
force-pushed
the
devx-1004-distinguish-proxied-tool-exits-from-lstk-errors-in-command
branch
from
September 24, 2026 08:32
3d88d9e to
b802329
Compare
Co-Authored-By: Claude <noreply@anthropic.com>
joe4dev
force-pushed
the
devx-1004-distinguish-proxied-tool-exits-from-lstk-errors-in-command
branch
from
September 25, 2026 07:11
97e23b2 to
42b8df6
Compare
2 tasks
…de and sink Co-Authored-By: Claude <noreply@anthropic.com>
Member
Author
joe4dev
commented
Sep 25, 2026
| @@ -0,0 +1,44 @@ | |||
| #!/usr/bin/env python3 | |||
Member
Author
There was a problem hiding this comment.
That addition makes it easy to smoke-test telemetry locally
joe4dev
commented
Sep 25, 2026
| // Commonly exported by LocalStack developers; inherited, it disables the | ||
| // telemetry client and every telemetry assertion times out. Tests | ||
| // covering the disabled path set it explicitly via With. | ||
| Without(DisableEvents). |
Member
Author
There was a problem hiding this comment.
Avoids breaking tests when LOCALSTACK_DISABLE_EVENTS=1 is set (which I do by default)
joe4dev
commented
Sep 25, 2026
| tel.EmitCommand(ctx, "ext:"+name, "", nil, time.Since(start).Milliseconds(), exitCode, errorMsg) | ||
| // An extension is lstk's own code shipped separately, not a wrapped | ||
| // third-party tool, so its exit is lstk's and the event is not proxied. | ||
| result := commandResult(ctx, runErr, false) |
Member
Author
There was a problem hiding this comment.
The new helper commandResult refactors several duplicates usages
joe4dev
commented
Sep 25, 2026
| } | ||
|
|
||
| if err := runErr; err != nil { | ||
| if err := proc.MarkUserToolExit(runErr); err != nil { |
Member
Author
There was a problem hiding this comment.
Explicitly mark each proxy (i.e., user tool) because lstk uses external tools internally
joe4dev
commented
Sep 25, 2026
|
|
||
| func (p *interruptWatcher) Write(b []byte) (int, error) { | ||
| if bytes.IndexByte(b, 0x03) >= 0 { | ||
| p.seen.Store(true) |
Member
Author
There was a problem hiding this comment.
That's the place where we capture the cancelled flag by detecting 0x03, everything gets forwarded directly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Motivation
lstk aws s3 lss(the user's typo, AWS CLI exit 252) andlstk aws s3 lsfailing because Docker is down produce identicallstk_commandevents, so the analytics error ranking counts users' CLI mistakes as lstk errors (DEVX-1003). The failure rate is skewed the same way: a successfullstk aws s3 lscannot be told from a successfullstk start. And a wrapped tool that traps Ctrl-C exits 1 or 130, which reads as a failure.Solution
Three raw fields on
lstk_command. Whose failure it was is derived in thefct_lstk_commandpipe, which has to derive it for historical rows anyway.parameters.proxiedaws,az,cdk,sam,terraform). Extensions are lstk's own code and are not proxiedresult.proxy_exit_coderesult.cancelledproxiedproxy_exit_code0252Origin is declared at the five proxy exec sites via
proc.MarkUserToolExit, not inferred from an*exec.ExitError, because lstk shells out for its own purposes too (brew,az cloud list,aws s3api create-bucket) and those exits readexit status Nexactly like the user's.azurecli.Exec(the user'slstk az) andazurecli.Run(lstk's own) are split so onlyExecmarks. AproxyCommandAnnotationon the five proxies replacesDisableFlagParsingas the proxied signal and also drivessubcommand.proc.RunInPTYrecords a forwarded Ctrl-C (proc.WasInterrupted) because on the interactive PTY path only the child receives SIGINT.Also:
make telemetry-sinkprints events locally, and the integration env no longer inherits an ambientLOCALSTACK_DISABLE_EVENTS=1that silently disabled every telemetry assertion.Telemetry Data
lstk aws s3 lssagainst a running emulator, captured withmake telemetry-sink(environmentblock omitted, it is unchanged).Before (
main):{ "parameters": {"command": "aws", "flags": null, "subcommand": "s3 lss"}, "result": {"duration_ms": 302, "error_msg": "exit status 252", "exit_code": 252} }After:
{ "parameters": {"command": "aws", "flags": null, "proxied": true, "subcommand": "s3 lss"}, "result": {"cancelled": false, "duration_ms": 214, "error_msg": "exit status 252", "exit_code": 252, "proxy_exit_code": 252} }The same command with Docker down records
"proxied": trueand noproxy_exit_code;lstk startrecords"proxied": false; a Ctrl-C'dlstk awswhose CLI exits 130 records"cancelled": true, "proxy_exit_code": 130.PTY exit-code capturing
The wrapped-tool PTY design (#476, full virtualization) routes an interactive Ctrl-C as a
0x03byte into the tool's own PTY, so only the tool receives SIGINT and lstk's signal context never fires.cancelledtherefore watches the pumped bytes:interruptWatcherforwards every byte unchanged and flags an ETX, andRunInPTYmarks the returned error (proc.WasInterrupted). Verified side by side on amainbuild and this PR with fake tools in a PTY: a SIGINT-counting tool receives exactly one interrupt (exit 41) on both; a raw-mode tool still gets0x03as a key; Ctrl-Z stays a no-op; piped stdin containing0x03passes through byte-exact and is not counted; the terminal is restored after every run. The existing pager and streaming PTY tests pass. Two known asymmetries, documented indesign.md: a raw-mode tool that consumes Ctrl-C as input and later exits non-zero reads as cancelled, and a Ctrl-C after which the tool exits 0 does not (the pipe readsexit_code = 0first either way).Analytics side
Design, pipe SQL, retroactive rule and panel changes:
openspec/changes/distinguish-proxied-command-errors/design.md. Readproxy_exit_codewithJSONHasfirst (JSONExtractIntreturns 0 for a missing key);JSONHas(parameters, 'proxied')marks a post-cutover row, so no version gating is needed. This supersedes theproxy_errorboolean discussed on DEVX-1004, per the DevX Weekly of 2026-09-21 ("the raw data rather than the interpretation"). The why axis (error_code/error_category) is the stacked #517.Docs
Nothing user-facing: the
lstk_commandpayload is internal and undocumented.make telemetry-sinkis developer tooling, documented inCLAUDE.md.Review
Human review advised: it changes a telemetry contract a pipe and two Grafana panels depend on, and the field shape departs from what the Linear thread agreed. An independent adversarial review at
42b8df6found no blockers; its hygiene findings are inb67ab27.Main questions for reviewers:
proxied=true|falseexplicitly or query it in Tinybird usingcommand IN ('aws','az','cdk','sam','terraform')(asked in #support-data here)? -> This PR suggests explicit tracking/marking as the responsibility of lstkexit_codeandproxy_exit_codeseparately when they are mostly the same? ->proxy_exit_codeprimarily carries presence information and could be replaced withproxy_error=true|false. Capturing the raw data makes no assumption (e.g., potential edge cases) and allows for direct interpretation.cancelled=true|falseexplicitly or guess it fromexit_code(typically 1 or 130) in Tinybird? -> This PR suggests explicit tracking because exit code rules would need to be tool-specific and could be pollutedTodo
localstack-dwh(fct_lstk_command.pipe) and the two Grafana panels once this ships🤖 Generated with Claude Code
Closes DEVX-1004