Skip to content

Two graff instances on one terminal corrupt each other's session and input (SIGTTOU suspend already fixed in #271) #289

Description

@justrach

Symptom

Running graff --yolo (or the TUI generally) sometimes dies immediately with:

zsh: suspended (tty output)  graff --yolo

Reproducible by racing two graff instances on one terminal - e.g. /resume-ing the same session from a second shell, or the GUI's graff sidecar starting while a CLI graff holds the terminal. Whichever instance touches termios second gets stopped.

Root cause

src/term.zig:144, in tty.enterRaw():

std.posix.tcsetattr(fd, .NOW, raw) catch return null;

POSIX: when a process in a background process group calls tcsetattr() on its controlling terminal, the kernel sends it SIGTTOU, whose default disposition is stop the process. This fires regardless of the terminal's tostop flag - tcsetattr from a background pgrp always raises it.

A terminal has exactly one foreground process group. Two graff instances sharing one tty means the second is background by definition, so it is stopped the instant it enters raw mode.

There is currently no SIGTTOU handling and no tcgetpgrp foreground check anywhere in the tree. tty.restore() (src/term.zig:155) has the same exposure on the way out.

Fix

  1. Ignore SIGTTOU around the tcsetattr calls in both enterRaw() and restore(). Save the prior disposition, install SIG_IGN, call, restore. The call then succeeds instead of stopping the process - correct here, since graff only ever sets its own terminal state. This alone removes the symptom.
  2. Foreground check at startup: if tcgetpgrp(STDIN_FILENO) != getpgrp(), graff is a background job. Emit a clear message (graff needs the foreground - run fg) or degrade to non-TUI mode, instead of silently suspending.
  3. Re-enter raw mode on SIGCONT, so a suspended-then-fg'd graff is not left with a broken terminal mode.
  4. Lock the session file. Two instances on one --resume / /resume session is a corruption race independent of the tty. Take an exclusive advisory lock on <name>.session.json; the second instance should report "session open in another graff" rather than racing. Same cross-process-lock pattern the OAuth credential writes need.

Notes

  • Windows is unaffected (console API, no process groups).
  • Zig 0.16: verify tcgetpgrp is exposed in std; may need ioctl(TIOCGPGRP) directly, same situation as chdir.
  • Items 1-3 are the tty fix. Item 4 is a separate concurrency bug that shares the repro and should probably be its own PR.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions