Run a verb from the controller's own running image, and refuse what it cannot run as a handover (hq issue 289)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

The witness moves a running build aside into a directory the controller's user
cannot enter, then deletes it; a verb exec'd from os.Executable() in that window
failed with permission denied. /proc/self/exe stays valid while the process lives.
A verb that still cannot start, or arrives while the controller stops, is refused
with link.ErrHandingOver and marked retry: handing-over.
This commit is contained in:
jochen
2026-10-07 02:45:09 +02:00
parent 9d15f3a39e
commit 6c7af5c63f
6 changed files with 278 additions and 5 deletions
+16 -4
View File
@@ -740,12 +740,18 @@ func isWhyFlag(word string) bool {
}
// runVerb runs this binary with the given command line and gathers what it said.
//
// **This binary is the image this process runs, never the file at the path it started from**
// (novox/hq issue 289): the node-engine's witness moves a running build aside when it places the next
// one, into a directory only it may enter, and deletes it once the next is proved — while this process
// may still be serving. A verb run from the path then failed with "permission denied" for the seconds
// the old controller still answered. selfCommand runs what this process is running, wherever its file
// went; a verb it still cannot start is refused as a handover, which the caller asks again.
func runVerb(ctx context.Context, argv []string) (verbAnswer, error) {
self, err := os.Executable()
if err != nil {
return verbAnswer{}, err
if handingOver.Load() {
return verbAnswer{}, fmt.Errorf("%w: this controller is stopping and runs no new command", link.ErrHandingOver)
}
cmd := exec.CommandContext(ctx, self, argv...)
cmd := selfCommand(ctx, argv)
// The same environment: the stores' credentials, the bus, the broker — everything a command run
// from a shell in this container would have, because it is that.
cmd.Env = os.Environ()
@@ -773,6 +779,12 @@ func runVerb(ctx context.Context, argv []string) (verbAnswer, error) {
var exit *exec.ExitError
if runErr != nil && !errors.As(runErr, &exit) {
// Not the command refusing — the command not running at all, which is this process's fault.
if errors.Is(runErr, os.ErrPermission) || errors.Is(runErr, os.ErrNotExist) {
// Its own image unreachable: a build replaced under a process that has not yet stopped.
// Refused as a handover, so the caller asks the controller that follows.
return answer, fmt.Errorf("%w: could not run %s from this controller's own build: %v",
link.ErrHandingOver, strings.Join(argv, " "), runErr)
}
return answer, fmt.Errorf("could not run %s: %w", strings.Join(argv, " "), runErr)
}
return answer, nil