Skip to content

Handle user cancellation in ssh agent-shim - #7050

Open
rclarey wants to merge 4 commits into
mainfrom
agent-shim-user-cancel
Open

rclarey wants to merge 4 commits into
mainfrom
agent-shim-user-cancel

Conversation

@rclarey

@rclarey rclarey commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Addresses this comment: #6883

Previously agent-shim telemetry did not accurately report the error category when a user cancelled via Ctrl-C

This PR also adds libs/process/signal.go which is the existing signal handling code used by air and environments setup-local moved to a shared place. Refactoring to make air and environments setup-local use the new shared lib lives in #7053

Why

So error categories are accurate

Tests

Added tests for new signal handling

Nothing on the agent-shim path handled signals, so Ctrl-C, SIGTERM, or SIGHUP
during setup terminated the process before the failure event was logged or
uploaded, and USER_ABORTED was unreachable. Cancel the context on the first
signal instead so the in-flight step aborts and the event is reported.

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot added the AIR Databricks AI Runtime CLI label Oct 14, 2026
rclarey and others added 3 commits October 14, 2026 13:24
agent-shim needs to cancel on the first signal and then restore the previous
disposition, so a second one gets its usual handling. environments setup-local
and air each carry a near-copy of that logic. Add one helper in libs/process,
modelled on signal.NotifyContext, and use it in place of agent-shim's own copy.
The second-signal regression test from environments is copied alongside it,
now covering SIGINT, SIGTERM, and SIGHUP.

Moving environments and air over to the helper is left to a follow-up.

Co-authored-by: Isaac <no-reply@databricks.com>
A signal that arrived while a step ignored ctx (warm bootstrap, file I/O) was
swallowed and the agent launched anyway. Restore the default signal handling
before committing to the launch, so a later signal kills the shim as it would
without a handler, then check whether a signal already came in. Checking first
would leave a window in which a signal is consumed by the handler and the
agent still launches.

stopSignals cancels ctx, so process.NotifyContext now cancels with a
*SignalError cause on a signal, and its stop still reports a signal that
arrived before it. The success event uses the caller's context.

probeAIGateway now returns the cancellation before classifying its results,
so an interrupted probe is no longer reported as a disabled gateway or a
transient error to retry.

Co-authored-by: Isaac <no-reply@databricks.com>
Cover a probe run on a cancelled context, and a signal during a warm-path
bootstrap step that swallows the cancellation: the shim must not exec the
agent and must report USER_ABORTED. Isolate HOME and provide the exec ID and
runtime detection cmd/root sets up, so a regression fails by launching the
stub agent rather than by panicking earlier.

Co-authored-by: Isaac <no-reply@databricks.com>
@rclarey
rclarey force-pushed the agent-shim-user-cancel branch from 394ff7b to 2006cfc Compare October 14, 2026 13:30
@rclarey rclarey removed the AIR Databricks AI Runtime CLI label Oct 14, 2026
@rclarey
rclarey added this pull request to stack #7054 October 14, 2026 13:37
@rclarey
rclarey marked this pull request as ready for review October 14, 2026 13:38
@rclarey
rclarey requested review from a team as code owners October 14, 2026 13:38
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 2006cfc

Run: 38572532365

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 276 15 10:48
✅​ aws-windows-latest-4core-16gb 278 13 8:31
✅​ azure linux-2core-8gb 275 15 9:21
✅​ azure-windows-latest-4core-16gb 277 13 9:04
✅​ gcp linux-2core-8gb 276 15 9:04
✅​ gcp-windows-latest-4core-16gb 278 13 6:07
Top 7 slowest tests (at least 2 minutes):
duration env testname
3:27 aws-windows-latest-4core-16gb TestAccept
3:09 gcp-windows-latest-4core-16gb TestAccept
3:05 azure-windows-latest-4core-16gb TestAccept
2:23 aws-windows-latest-4core-16gb TestFilerWorkspaceFilesExtensionsReadDir
2:16 aws-windows-latest-4core-16gb TestImportDir
2:11 azure-windows-latest-4core-16gb TestExportDir
2:08 gcp linux-2core-8gb TestFilerWorkspaceNotebook/pythonJupyterNb.ipynb

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two cancellation paths still lose or misclassify USER_ABORTED telemetry. Both reproduced against 2006cfc. The affected package tests and focused race-detector checks pass.

// late signals get the default handling again.
parentCtx := ctx
ctx, stopSignals := process.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
defer stopSignals()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Join the signal handler before classifying failures

The failure telemetry defer runs before stopSignals(), so it can read ctx.Err() while the signal goroutine is still processing an interrupt. If an installer exits at the same time, cmd.Run() can return first and the event gets the install failure category.

I reproduced this with a fake uv that runs kill -INT "$PPID" followed by exit 1. With GOMAXPROCS=1, iteration 77 uploaded UNITY_GATEWAY_CLI_INSTALL_FAILED instead of USER_ABORTED. The handler is joined after the incorrect event has already been buffered.

We should stop/join the handler before building the failure outcome, then use the signal cause and parent context to distinguish an interrupt from the cancellation caused by stopSignals() itself.

// the launch is committed,
// late signals get the default handling again.
parentCtx := ctx
ctx, stopSignals := process.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Cancel installer descendants before waiting for output

The shim now intercepts the first SIGTERM/SIGHUP, but cancellation in runCommand still uses exec.CommandContext's default behavior, which kills only the direct child. If uv tool install has a descendant holding stdout or stderr open, cmd.Run() keeps waiting for EOF with no WaitDelay. The shim doesn't return or upload the USER_ABORTED event until that descendant exits.

I reproduced this with a fake uv that backgrounds a sleep, sends SIGTERM to the shim, then waits. The shim stayed blocked after cancellation and returned only when I manually killed the grandchild.

We should cancel the installer process group and bound the wait, including cleanup of surviving descendants. The existing process.WithProcessGroup() support covers this behavior.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants