Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Make Shell.clear() safe for non-TTY stdout - #37

Merged
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty
May 25, 2026
Merged

Make Shell.clear() safe for non-TTY stdout#37
makermelissa merged 1 commit into
adafruit:mainfrom
makermelissa-ai-assistant:fix/clear-no-tty

Conversation

@makermelissa-ai-assistant

@makermelissa-ai-assistantmakermelissa-ai-assistant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Shell.clear() currently calls os.system("clear") unconditionally. When the
script is invoked from a non-interactive context — output piped or redirected,
sudo with a stripped environment, CI runners, an automation/agent harness,
etc. — ncurses' clear binary runs with a TERM value that its terminfo
database can't resolve, and prints a visible cosmetic error to stderr:

'unknown': I need something more specific.

That line shows up at the top of installer output for anyone running the
Adafruit installers (e.g. adafruit-pitft.py) non-interactively on Debian
Trixie. It's harmless but confusing — it looks like a real error.

Fix

Treat "clear the screen" as a no-op when there is effectively no screen:

  • stdout is not a TTY (piped/redirected/CI/agent harness) → return early.
  • TERM is missing, dumb, or unknown (sudo env -i, sudo with a
    stripped environment, etc.) even on a real TTY → return early.
  • Interactive TTY with a usable TERM → behavior unchanged: shell out to
    clear so the user gets a real terminal reset (including scrollback flush
    where the emulator supports it).

An earlier revision of this PR fell back to emitting the ANSI RIS escape
sequence (\033c) on the non-TTY path. That was dropped after review feedback
pointed out it would write control bytes into captured/redirected output. A
no-op is the right answer: there is no screen to clear, so we just don't.

Testing

Verified locally on Trixie with a small driver that exercises all four guard
branches (monkeypatching sys.stdout.isatty and os.environ["TERM"]):

stdoutTERMResult
not a TTY(any)no-op, no calls to os.system
TTYunsetno-op
TTYdumbno-op
TTYunknownno-op
TTYxterm-256coloros.system("clear") (original behavior)

After applying the fix, the spurious
'unknown': I need something more specific. message no longer appears at the
top of installer output when running under non-interactive sudo.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Shell.clear() to avoid emitting ncurses/terminfo errors when clear is executed in non-interactive environments (piped/redirected output, CI, stripped environments), by gating the clear invocation on whether stdout is a TTY and adding a fallback behavior.

Changes:

  • Update Shell.clear() docstring to document interactive vs non-interactive behavior.
  • Call os.system("clear") only when sys.stdout.isatty() is true.
  • For non-TTY stdout, write the ANSI RIS escape sequence (\033c) and flush.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadadafruit_shell.py Outdated
Comment on lines +444 to +454
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated to make the non-TTY branch a plain no-op instead of writing the RIS escape into redirected stdout. Clearing has no meaning when there is no screen to clear, so we just return early.

Comment threadadafruit_shell.py Outdated
Comment on lines 441 to 455
On an interactive TTY, defer to the ``clear`` binary so the user
gets a real terminal reset (including scrollback flush where the
emulator supports it). When stdout is not a TTY (output piped or
redirected, ``sudo`` with a stripped environment, CI, etc.), fall
back to the ANSI RIS escape sequence (``\\033c``) so the call is
still harmless and doesn't trigger ncurses' ``'unknown': I need
something more specific.`` error when ``TERM`` is missing or set
to a value the local terminfo doesn't know.
"""
if sys.stdout.isatty():
os.system("clear")
else:
sys.stdout.write("\033c")
sys.stdout.flush()

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — also addressed in the updated patch. Even on a real TTY, if TERM is empty, dumb, or unknown (e.g. sudo env -i), we now skip os.system("clear") entirely. So clear only runs when both stdout is a TTY and TERM looks usable; otherwise it's a no-op and no ncurses error can escape.

When adafruit_shell.Shell.clear() is invoked from a script whose stdout
is not a real terminal (output piped or redirected, sudo with a
stripped environment, CI, etc.), the unconditional os.system("clear")
call falls through to ncurses' clear binary with a TERM that the local
terminfo database can't resolve. On Debian Trixie this surfaces as a
visible cosmetic error at the top of installer output:
'unknown': I need something more specific.
Treat "clear the screen" as a no-op when there's effectively no screen:
* stdout is not a TTY (piped/redirected/CI/agent harness), or
* TERM is unset / 'dumb' / 'unknown' (sudo with stripped env, env -i,
etc.), even on a real TTY.
In both cases there is nothing meaningful to clear, so we skip the
os.system("clear") call entirely. On an interactive TTY with a usable
TERM, behavior is unchanged: full terminal reset via the clear binary.
This also avoids writing control bytes into redirected stdout, which an
earlier draft of this fix would have done.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment threadadafruit_shell.py
Comment on lines +445 to +456
When stdout isn't a TTY (output piped or redirected, CI, etc.)
or ``TERM`` is missing/unknown (``sudo`` with a stripped
environment, ``env -i``, ...), do nothing. Clearing has no
meaning when there's no screen to clear, and unconditionally
shelling out to ``clear`` in those cases prints ncurses'
``'unknown': I need something more specific.`` to stderr.
"""
if not sys.stdout.isatty():
return
term = os.environ.get("TERM", "")
if not term or term in {"dumb", "unknown"}:
return

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — PR description updated to match the current implementation (no-op on non-TTY / unusable TERM, no RIS fallback).

@makermelissa
makermelissa merged commit 344b44a into adafruit:mainMay 25, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@makermelissa-ai-assistant@makermelissa