diff --git a/cmd/agentbbs/main.go b/cmd/agentbbs/main.go index 261bfb1..a329c56 100644 --- a/cmd/agentbbs/main.go +++ b/cmd/agentbbs/main.go @@ -194,8 +194,9 @@ func main() { } // router dispatches a session by username (PRD §4.4 + pods addendum). -// The active-PTY guard applies to hub sessions only: join@ must work without -// a terminal (it prints and disconnects), and pod@ checks its PTY itself. +// The active-PTY guard applies to hub sessions only; join@ and pod@ check their +// own PTY (both are interactive) so they can return a tailored hint instead of +// activeterm's opaque rejection. func (a *app) router() wish.Middleware { btMw := bm.Middleware(a.teaHandler) adminMw := bm.Middleware(a.adminTeaHandler) @@ -291,6 +292,15 @@ func (a *app) handleJoin(s ssh.Session) { _ = s.Exit(1) return } + // Onboarding reads an email and a verification code interactively, so it + // needs a terminal. Without a PTY the prompts would block forever (e.g. ssh + // launched with no controlling tty, which delegates prompts to ssh-askpass). + // Fail fast with a hint instead of hanging. + if _, _, hasPty := s.Pty(); !hasPty { + wish.Println(s, "join@ is interactive — reconnect with a terminal: ssh -t join@"+a.host) + _ = s.Exit(1) + return + } u, found, err := a.st.UserByFingerprint(fp) if err == nil && !found { name := "member-" + strings.ToLower(strings.TrimPrefix(fp, "SHA256:"))[:8]