From c90b02b9dd583d81c8937213ab8f34d63af27927 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Mon, 11 May 2026 13:35:10 -0500 Subject: [PATCH 1/4] Document TTY-detection limitation for goaccess, tqdm, etc. duct starts the wrapped command with start_new_session=True and pipes stdout/stderr instead of a PTY, so isatty() in the child is false. Programs that adapt their output to that signal (goaccess refusing to read piped stdin, tqdm switching to its rate-limited non-TTY path) appear broken under duct. Add a FAQ entry that names the limitation, gives concrete workarounds for both tools, and shows the setsid recipe so users can reproduce and develop against the non-interactive path without duct. Refs: #261, #426 Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 3a00912d..85a9fd26 100644 --- a/README.md +++ b/README.md @@ -290,3 +290,22 @@ $ duct --log-level DEBUG echo "hello world" By default, [git-annex](https://git-annex.branchable.com/) treats all dotfiles, and files under directories starting with a `.` as "small" regardless of `annex.largefiles` setting [[ref: an issue describing the logic](https://git-annex.branchable.com/bugs/add__58___inconsistently_treats_files_in_dotdirs_as_dotfiles/?updated#comment-efc1f2aa8f46e88a8be9837a56cfa6f7)]. It is necessary to set `annex.dotfiles` variable to `true` to make git-annex treat them as regular files and thus subject to `annex.largefiles` setting [[ref: git-annex config](https://git-annex.branchable.com/git-annex-config/)]. Could be done the repository (not just specific clone, but any instance since records in `git-annex` branch) wide using `git annex config --set annex.dotfiles true`. + +### My command behaves differently under duct (`goaccess` fails, `tqdm` "freezes", progress bars look wrong) + +duct starts the wrapped command in a new session (`start_new_session=True`), so the child has no controlling terminal, and duct attaches pipes to its stdout/stderr instead of a PTY. +Programs that inspect `isatty()` to decide how to behave will therefore take their non-interactive path under duct. +Two common cases: + +- [`goaccess`](https://goaccess.io) refuses to read piped stdin without an explicit `-` argument and exits with `No input data was provided` [[ref: con/duct#261](https://github.com/con/duct/issues/261)]. + Pass `-` (e.g. `... | goaccess - --log-format=AWSS3 -o report.html`) to force stdin mode. +- [`tqdm`](https://tqdm.github.io) drops `\r`-overwrite redraws and switches to its rate-limited non-TTY path, so updates print one line at a time and arrive in bursts seconds apart — it looks like the bar has frozen [[ref: con/duct#426](https://github.com/con/duct/issues/426)]. + Tune `mininterval`/`maxinterval`/`miniters` on the `tqdm()` call, or pass `disable=False` together with the rate options, to get more frequent output in log mode. + +To check whether a given problem is a TTY issue and not a duct bug, reproduce it outside duct with `setsid`, which similarly detaches the controlling terminal: + +```bash +setsid bash -c "your command here" < /dev/null > out.log 2>&1 +``` + +If the symptom reproduces under `setsid`, develop and test the non-interactive flag set there; the command will then behave the same way under duct. From 4a7d602f4b351b594c50762ea41d306797575455 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Mon, 11 May 2026 13:58:53 -0500 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85a9fd26..549b7cd3 100644 --- a/README.md +++ b/README.md @@ -293,11 +293,12 @@ Could be done the repository (not just specific clone, but any instance since re ### My command behaves differently under duct (`goaccess` fails, `tqdm` "freezes", progress bars look wrong) -duct starts the wrapped command in a new session (`start_new_session=True`), so the child has no controlling terminal, and duct attaches pipes to its stdout/stderr instead of a PTY. +By default, duct starts the wrapped command in a new session (`start_new_session=True`; `--mode new-session`), so the child has no controlling terminal, and duct attaches pipes to its stdout/stderr instead of a PTY. Programs that inspect `isatty()` to decide how to behave will therefore take their non-interactive path under duct. +If you run duct with `--mode current-session`, this session detachment does not happen. Two common cases: -- [`goaccess`](https://goaccess.io) refuses to read piped stdin without an explicit `-` argument and exits with `No input data was provided` [[ref: con/duct#261](https://github.com/con/duct/issues/261)]. +- Under duct, [`goaccess`](https://goaccess.io) may require an explicit `-` argument to force stdin mode; otherwise it can exit with `No input data was provided` in this non-TTY/piped setup [[ref: con/duct#261](https://github.com/con/duct/issues/261)]. Pass `-` (e.g. `... | goaccess - --log-format=AWSS3 -o report.html`) to force stdin mode. - [`tqdm`](https://tqdm.github.io) drops `\r`-overwrite redraws and switches to its rate-limited non-TTY path, so updates print one line at a time and arrive in bursts seconds apart — it looks like the bar has frozen [[ref: con/duct#426](https://github.com/con/duct/issues/426)]. Tune `mininterval`/`maxinterval`/`miniters` on the `tqdm()` call, or pass `disable=False` together with the rate options, to get more frequent output in log mode. From 8e1aed0f3a5764337795d102ee2e84dc2d995b39 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Mon, 11 May 2026 14:02:48 -0500 Subject: [PATCH 3/4] fixup! Document TTY-detection limitation for goaccess, tqdm, etc. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 549b7cd3..c50abeef 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,7 @@ Two common cases: To check whether a given problem is a TTY issue and not a duct bug, reproduce it outside duct with `setsid`, which similarly detaches the controlling terminal: ```bash -setsid bash -c "your command here" < /dev/null > out.log 2>&1 +setsid bash -c "your command here" > out.log 2>&1 ``` If the symptom reproduces under `setsid`, develop and test the non-interactive flag set there; the command will then behave the same way under duct. From b12a3230b3db9d1f0b359694df3e7de3e1a6477f Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Mon, 11 May 2026 14:17:04 -0500 Subject: [PATCH 4/4] fixup! Document TTY-detection limitation for goaccess, tqdm, etc. --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c50abeef..cc958140 100644 --- a/README.md +++ b/README.md @@ -291,17 +291,16 @@ By default, [git-annex](https://git-annex.branchable.com/) treats all dotfiles, It is necessary to set `annex.dotfiles` variable to `true` to make git-annex treat them as regular files and thus subject to `annex.largefiles` setting [[ref: git-annex config](https://git-annex.branchable.com/git-annex-config/)]. Could be done the repository (not just specific clone, but any instance since records in `git-annex` branch) wide using `git annex config --set annex.dotfiles true`. -### My command behaves differently under duct (`goaccess` fails, `tqdm` "freezes", progress bars look wrong) +### My command behaves differently under duct (e.g. `goaccess` fails, progress bars look off) By default, duct starts the wrapped command in a new session (`start_new_session=True`; `--mode new-session`), so the child has no controlling terminal, and duct attaches pipes to its stdout/stderr instead of a PTY. -Programs that inspect `isatty()` to decide how to behave will therefore take their non-interactive path under duct. +Programs that inspect `isatty()` to decide how to behave will therefore take their non-interactive path under duct, and the result may be surprising. If you run duct with `--mode current-session`, this session detachment does not happen. -Two common cases: +Two examples: - Under duct, [`goaccess`](https://goaccess.io) may require an explicit `-` argument to force stdin mode; otherwise it can exit with `No input data was provided` in this non-TTY/piped setup [[ref: con/duct#261](https://github.com/con/duct/issues/261)]. Pass `-` (e.g. `... | goaccess - --log-format=AWSS3 -o report.html`) to force stdin mode. -- [`tqdm`](https://tqdm.github.io) drops `\r`-overwrite redraws and switches to its rate-limited non-TTY path, so updates print one line at a time and arrive in bursts seconds apart — it looks like the bar has frozen [[ref: con/duct#426](https://github.com/con/duct/issues/426)]. - Tune `mininterval`/`maxinterval`/`miniters` on the `tqdm()` call, or pass `disable=False` together with the rate options, to get more frequent output in log mode. +- Progress-bar libraries such as [`tqdm`](https://tqdm.github.io) may render differently when stdout/stderr is a pipe rather than a TTY [[ref: con/duct#426](https://github.com/con/duct/issues/426)]. To check whether a given problem is a TTY issue and not a duct bug, reproduce it outside duct with `setsid`, which similarly detaches the controlling terminal: