Repository: denoland/deno
Pull request: fix(ext/node): avoid env permission for TERM in console and readline

Fixes #30372
Related PR: #29472

Hello everyone,

Accessing `process.env.TERM` in Node polyfills currently requires the `--allow-env=TERM` permission in Deno.
This was previously fixed for `createInterface` in #29472, but the same issue remains in `console/constructor.mjs` and `readline/interface.mjs`.

This PR updates both files to use `op_get_env_no_permission_check("TERM")`, so TERM can be read without requiring additional permissions.
This resolves the issue where Node readline can get stuck if Deno prompts for environment permission (#30372).

<details>
<summary>Interactive Tests</summary>

```typescript
import * as readline from 'node:readline';
import { stdin as input, stdout as output } from 'node:process';

const rl = readline.createInterface({ input, output });

rl.question('What do you think of Node.js? ', (answer) => {
  console.log(`Thank you for your valuable feedback: ${answer}`);

  rl.close();
});
```

```bash
What do you think of Node.js? foo
Thank you for your valuable feedback: foo
```

```typescript
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';

const rl = readline.createInterface({ input, output });

const answer = await rl.question('What do you think of Node.js? ');

console.log(`Thank you for your valuable feedback: ${answer}`);

rl.close();
```

```bash
What do you think of Node.js? foo
Thank you for your valuable feedback: foo

```

```typescript
import readline from "node:readline";
import process from "node:process";
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.prompt();

rl.on("line", (input) => {
  console.log(`Received: ${input}`);
  rl.prompt();
});

rl.on("close", () => {
  console.log("Exiting...");
});
```

```bash
> foo
Received: foo
> bar
Received: bar
> Exiting...
```

</details>

thanks,

Changed files:
filename: ext/node/polyfills/internal/console/constructor.mjs
patch: @@ -1,7 +1,10 @@
 // Copyright 2018-2025 the Deno authors. MIT license.
 // Copyright Joyent and Node contributors. All rights reserved. MIT license.
 
-import { op_preview_entries } from "ext:core/ops";
+import {
+  op_get_env_no_permission_check,
+  op_preview_entries,
+} from "ext:core/ops";
 import { primordials } from "ext:core/mod.js";
 import { emitWarning } from "node:process";
 const {
@@ -500,7 +503,9 @@ const consoleMethods = {
   clear() {
     // It only makes sense to clear if _stdout is a TTY.
     // Otherwise, do nothing.
-    if (this._stdout.isTTY && process.env.TERM !== "dumb") {
+    if (
+      this._stdout.isTTY && op_get_env_no_permission_check("TERM") !== "dumb"
+    ) {
       cursorTo(this._stdout, 0, 0);
       clearScreenDown(this._stdout);
     }

filename: ext/node/polyfills/internal/readline/interface.mjs
patch: @@ -24,6 +24,8 @@
 // deno-lint-ignore-file prefer-primordials
 // deno-lint-ignore-file camelcase no-inner-declarations no-this-alias
 
+import { op_get_env_no_permission_check } from "ext:core/ops";
+
 import {
   ERR_INVALID_ARG_VALUE,
   ERR_USE_AFTER_CLOSE,
@@ -402,7 +404,7 @@ export class Interface extends InterfaceConstructor {
    */
   prompt(preserveCursor) {
     if (this.paused) this.resume();
-    if (this.terminal && process.env.TERM !== "dumb") {
+    if (this.terminal && op_get_env_no_permission_check("TERM") !== "dumb") {
       if (!preserveCursor) this.cursor = 0;
       this[kRefreshLine]();
     } else {