check / check (pull_request) Successful in 1m22s
On SIGINT/SIGTERM the process could exit before the interrupted command's cleanup defers ran, leaving decrypted data in the temp directory: the blob cache (vaultik-blobcache-*) and the decrypted snapshot database (vaultik-restore-*). RunApp now mirrors fx's run sequence: start, block on app.Wait(), then app.Stop(), returning only after Stop completes. fx delivers both an OS interrupt and the finished operation's Shutdowner.Shutdown() on that one channel. Stop runs the OnStop hooks; the operation's hook cancels the command and waits for its goroutine to return (bounded by shutdownTimeout) before exit. The old code returned as soon as app.Done fired, without Stop, so on a real interrupt it unwound to os.Exit while cleanup still ran and the wait never blocked exit. Restore's loops check the context between chunks and blobs so the wait ends promptly. A cli test drives RunApp through the OnStop hook and asserts the scratch file is gone before RunApp returns. Model: opus-4-8