metadata:pull: detect non-interactive stdout (or print a clearer hint) when prompting to overwrite store.config.json
- Dominant language
- TypeScript
- Stars
- 1.4k
- Forks
- 236
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 98
Description
### Summary
When `store.config.json` already exists locally, `eas metadata:pull` prompts:
```
Do you want to overwrite the existing "store.config.json"?
```
and waits on stdin. The prompt is correctly skipped when `--non-interactive` is passed (or when `EASNonInteractiveFlag`'s default kicks in via `boolish('CI', false) || !process.stdin.isTTY`). The problem is that there are common environments where `process.stdin` is genuinely a TTY but no human is going to answer:
- Running inside a VS Code / Copilot Chat / similar AI-agent integrated terminal. The shell is wrapped in a pty, so `process.stdin.isTTY === true`, but the agent has no way to type `y` and the run stalls indefinitely.
- Running interactively with stdout piped (`eas metadata:pull --profile production 2>&1 | tee log`). stdout is no longer a TTY, but stdin is, and `EASNonInteractiveFlag` only checks stdin. The prompt still appears (often hidden by the pipe / spinner). The run looks indistinguishable from a hang.
- Running inside `nohup` without an explicit `< /dev/null`, where stdin behavior depends on the shell and stdin may still be inherited from the controlling terminal.
There is no inline hint to the user that they need `--non-interactive`. Documentation mentions the flag, but the failure mode in the wild is "the command appears to hang", and unlike `metadata:push` it cannot be made progress on by Ctrl-C + retry — the user first has to figure out the prompt.
### Concrete repro
```bash
# Local store.config.json already exists.
$ eas metadata:pull --profile production 2>&1 | tee /tmp/pull.log
✔ Linked to project @/
EAS Metadata is in beta and subject to breaking changes.
# (hangs)
```
There is no banner, no countdown, and no hint that `--non-interactive` would auto-overwrite. The prompt itself is sometimes obscured by piping and intermediate spinner output.
### Why both the workaround and the design are awkward
- The current behavior is "correct" given the current heuristic (`process.stdin.isTTY` is true → assume a human can answer), but in practice this command is overwhelmingly invoked from scripts, CI, AI agents, or piped invocations. Among those cases, only "real CI with stdin redirected" is currently autodetected.
- A user who hits this for the first time will commonly mistake the hang for a credentials issue, an Apple server issue, or a network issue.
### Suggestion
Either of these would help. They are not mutually exclusive:
1. **Auto-non-interactive when stdout is not a TTY.** Treat `!process.stdout.isTTY` as a strong signal that no human is going to see the prompt. `metadata:pull`'s `nonInteractive` default would then become roughly:
```ts
default: () =>
boolish('CI', false) ||
!process.stdin.isTTY ||
!process.stdout.isTTY,
```
This is a behavior change so it should probably ship behind a release note, but it matches what almost every user actually wants.
2. **Print an inline hint before showing the overwrite prompt**, so the failure mode is "agent prints a hint and stops" rather than "agent hangs silently". For example, immediately before `confirmAsync(...)` in `packages/eas-cli/src/metadata/download.ts`:
```ts
if (fileExists) {
if (nonInteractive) {
Log.log(`Overwriting existing store config at "${path.relative(projectDir, filePath)}".`);
} else {
Log.log(
chalk`{dim Tip: pass {bold --non-interactive} to auto-overwrite ` +
chalk`{bold ${path.relative(projectDir, filePath)}} without prompting.}`
);
const overwrite = await confirmAsync({ … });
…
}
}
```
That alone makes the hang self-explanatory in agent / piped contexts.
3. (Optional) **Default `metadata:pull` to overwrite-with-warning** when `store.config.json` already exists and points at the same `appId` as the credentials are configured for. Track-record-wise, the existing file in this command's case is almost always exactly what the user wants to refresh.
### Environment
```
eas-cli: 18.11.0 darwin-arm64 node-v22.13.1
node: v22.13.1
macOS: 26.4.1 (arm64)
```
### Related code
- `packages/eas-cli/src/metadata/download.ts` (the `confirmAsync` site)
- `packages/eas-cli/src/commandUtils/flags.ts` (`isNonInteractiveByDefault`, `EASNonInteractiveFlag`)
Contributor guide
Research direction
Start with the confirmAsync site in packages/eas-cli/src/metadata/download.ts, then read isNonInteractiveByDefault and EASNonInteractiveFlag in packages/eas-cli/src/commandUtils/flags.ts. Reproduce the piped metadata:pull command with an existing store.config.json and confirm the chosen behavior or hint is visible and no longer appears to hang. Check the relevant CLI tests and include a release note if the default behavior changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- cli, developer-experience
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100