Why this only breaks in triggers
SpreadsheetApp.getUi() requires an active editor session — a human with a browser tab open on the spreadsheet. When you click Run in the Apps Script IDE or open the sheet and trigger onOpen(), that session exists. When a time-driven trigger fires at 2 AM, or when onFormSubmit() runs because someone submitted a form, there is no browser tab, no human session, and therefore no UI context. Google's runtime refuses the call immediately with that error.
This is a context error, not a permissions error. Adding extra OAuth scopes or changing deployment settings will not fix it. The function genuinely cannot reach a UI that does not exist.