Skip to content

feat(hooks): hooks have no timeout — a hung hook hangs the release indefinitely #801

Description

@BryanFRD

run_hook (src/hooks/runner.rs) spawns user shell with cmd.status() or cmd.output() and waits, unbounded. Any hook that blocks forever blocks the release forever, holding .git/ferrflow.lock for the duration.

This is not exotic. The ways a hook hangs in CI are all mundane:

  • A command that prompts on stdin (npm login, gpg asking for a passphrase, a git operation that falls through to a credential prompt). stdin is inherited from the release process, so it waits on a terminal that will never answer.
  • A hook that curls an endpoint with no --max-time.
  • A docker build or test suite that deadlocks.

In the non-verbose path (cmd.output()) there is no output at all while this happens — output is buffered until the process exits — so the job looks frozen with no indication of which hook is responsible. The job eventually dies on the CI wall-clock limit, and then the lock issue applies on top.

function hooks in ferrflow.js / ferrflow.ts are reified into node --input-type=module -e "…" commands and go down the same path, so they inherit the problem.

Proposal

  • Add a per-hook timeout, configurable at both levels: workspace.hooks.timeout as a default and a per-hook override, following the shape onFailure already uses. Default to something generous — 10 or 15 minutes — since build hooks are legitimately slow; the point is to bound it, not to be strict.
  • On timeout, kill the process group (not just the child — a hook that backgrounds work leaves orphans otherwise) and route through the existing handle_failure so onFailure: continue still behaves as configured.
  • Close stdin on hook processes (Stdio::null()) unless explicitly opted in. A release hook has no business reading from a terminal, and this alone removes the most common hang.
  • Stream output in the non-verbose path, or at minimum print a "still running after Ns" heartbeat, so a slow hook is distinguishable from a hung one.

Tests

  • A hook that sleeps past the timeout is killed and reported as a hook failure with the timeout in the message.
  • onFailure: continue on a timed-out hook continues the release.
  • A hook reading stdin gets EOF immediately rather than blocking.
  • A hook that spawns a background child leaves no orphan after the timeout kill.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium priorityenhancementImprovement to existing feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions