Skip to content

clearConsole emits a terminal reset (ESC c) on every scala-cli run, corrupting captured output and wiping scrollback #463

Description

@soronpo

Summary

AppOptions.ClearConsole defaults to true whenever scala-cli is detected, so every
DFHDL run launched via scala run prints ESC c (RIS, full terminal reset) before the
welcome banner. On an interactive terminal this clears the screen and scrollback on
every compile. In captured or non-TTY output it corrupts the first line, which surfaces
as a stray c prefix:

cWelcome to DFiant HDL (DFHDL) v0.22.0+39-03afffb1-SNAPSHOT !!!

Source

lib/src/main/scala/dfhdl/options/AppOptions.scala:34

object ClearConsole:
  given ClearConsole = if (metalsIsRunning || scala_cliIsRunning) true else false

lib/src/main/scala/dfhdl/app/DFApp.scala:451

def run(commandArgs: Array[String]): Unit =
  if (appOptions.clearConsole) print("�c")

internals/src/main/scala/dfhdl/internals/helpers.scala:410

lazy val scala_cliIsRunning: Boolean = getShellCommand.exists(_.contains(".scala-build"))

The detection is of scala-cli, not of an interactive terminal, so the reset fires
regardless of whether anything can meaningfully be "cleared".

Reproduction

Any scala-cli invocation of a DFHDL design reproduces it. Minimal case:

//> src/AndGate.scala
import dfhdl.*

class AndGate extends EDDesign:
  val a, b = Bit <> IN
  val o    = Bit <> OUT

  o <> a && b
end AndGate
scala run src/ --scala 3.8.4 \
  --dep "io.github.dfianthdl::dfhdl::$VERSION" \
  --compiler-plugin "io.github.dfianthdl:::dfhdl-plugin::$VERSION" \
  -- commit | cat -A | head -3

The banner line is prefixed with a raw ESC (^[):

^[cWelcome to DFiant HDL (DFHDL) v0.22.0+39-03afffb1-SNAPSHOT !!!$

Impact

Found while running DFHDL non-interactively from an automated agent harness, where
scala run is the standard compile path.

  • Captured logs are corrupted. Anything that reads DFHDL output as text sees a stray
    c glued to the banner. Log parsers keying on the banner line fail.
  • Interactive scrollback is destroyed. RIS is a full reset, not a clear: on a real
    terminal every compile discards the history above it, including the output of the
    command the user just ran.
  • It cannot be avoided from the outside. The reset is printed before the command line
    is parsed, so no flag passed on that command line can suppress it.

Suggested fix

Gate the reset on stdout actually being an interactive terminal, in addition to the
existing tool detection. Something along the lines of:

given ClearConsole =
  (metalsIsRunning || scala_cliIsRunning) && System.console() != null

System.console() returns null when stdout is redirected or piped, which is precisely
the case where the reset does harm and no good. That preserves the current behaviour for
an interactive scala-cli or Metals session while leaving captured output clean.

A more conservative alternative is to keep the current default but honour an environment
variable (say DFHDL_NO_CLEAR_CONSOLE), though that still leaves the default wrong for
every non-TTY consumer unless they know to set it.

Environment

  • DFHDL 0.22.0+39-03afffb1-SNAPSHOT (built from training at 03afffb1)
  • Scala 3.8.4, scala-cli 1.11.0, JDK 21
  • Linux, non-interactive shell

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions