age encrypt -o and age decrypt -o (internal/cli/age/age.go, output and finish) always write a new file beside the named path and then rename it over that path. A symlink or a named pipe there is replaced by a regular file, and a reader of the pipe waits forever; -o /dev/stdout fails with "permission denied"; run as root, as in the development image, -o /dev/null replaces /dev/null with a 0600 file holding the ciphertext. An existing file also ends up with mode 0600, which the README does not say.
Work, on a branch cut from next, PR to next:
Look at the -o path without following a final symlink. Missing, or a regular file: write beside it and rename over it, as now. A symlink: apply the same rule to what it points at, so the link keeps pointing where it did. Anything else (a named pipe, a device such as /dev/null or /dev/stdout): write to it directly, with no file beside it and no rename.
The signal handling decided in #48 (comment) stays for the rename case; for a path written directly, a signal ends the tool like any other command.
README, keyfunc age encrypt and Errors: an existing regular file is replaced and the new one has mode 0600; a symlink is followed; a pipe or device is written directly, and the no-unfinished-file promise applies only to a new or regular file.
Definition of done
Tests cover a symlink to a regular file (the link survives and its target holds the output) and a named pipe at the -o path (a reader gets the output and the pipe is still a pipe).
-o /dev/null and -o /dev/stdout work and leave those paths as they were, checked by hand as root in the development image.
make check passes.
Model: opus-5-5
From the second whole-branch review of https://git.eeqj.de/sneak/keyfunc/pulls/28 (https://git.eeqj.de/sneak/keyfunc/pulls/28#issuecomment-123640):
`age encrypt -o` and `age decrypt -o` (`internal/cli/age/age.go`, `output` and `finish`) always write a new file beside the named path and then rename it over that path. A symlink or a named pipe there is replaced by a regular file, and a reader of the pipe waits forever; `-o /dev/stdout` fails with "permission denied"; run as root, as in the development image, `-o /dev/null` replaces `/dev/null` with a `0600` file holding the ciphertext. An existing file also ends up with mode `0600`, which the README does not say.
Work, on a branch cut from `next`, PR to `next`:
- Look at the `-o` path without following a final symlink. Missing, or a regular file: write beside it and rename over it, as now. A symlink: apply the same rule to what it points at, so the link keeps pointing where it did. Anything else (a named pipe, a device such as `/dev/null` or `/dev/stdout`): write to it directly, with no file beside it and no rename.
- The signal handling decided in https://git.eeqj.de/sneak/keyfunc/issues/48#issuecomment-122586 stays for the rename case; for a path written directly, a signal ends the tool like any other command.
- README, `keyfunc age encrypt` and Errors: an existing regular file is replaced and the new one has mode `0600`; a symlink is followed; a pipe or device is written directly, and the no-unfinished-file promise applies only to a new or regular file.
## Definition of done
- Tests cover a symlink to a regular file (the link survives and its target holds the output) and a named pipe at the `-o` path (a reader gets the output and the pipe is still a pipe).
- `-o /dev/null` and `-o /dev/stdout` work and leave those paths as they were, checked by hand as root in the development image.
- `make check` passes.
Model: opus-5-5
clawbot
self-assigned this 2026-10-04 14:59:23 +02:00
Built in #60: the -o path is looked at once; a missing path or a regular file is replaced by rename as before, a symlink gets the same treatment for what it points at, and a pipe or device is written directly. A symlink that points at nothing is refused, a judgement call noted on the PR.
Model: opus-5-5
Built in https://git.eeqj.de/sneak/keyfunc/pulls/60: the `-o` path is looked at once; a missing path or a regular file is replaced by rename as before, a symlink gets the same treatment for what it points at, and a pipe or device is written directly. A symlink that points at nothing is refused, a judgement call noted on the PR.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
From the second whole-branch review of #28 (#28 (comment)):
age encrypt -oandage decrypt -o(internal/cli/age/age.go,outputandfinish) always write a new file beside the named path and then rename it over that path. A symlink or a named pipe there is replaced by a regular file, and a reader of the pipe waits forever;-o /dev/stdoutfails with "permission denied"; run as root, as in the development image,-o /dev/nullreplaces/dev/nullwith a0600file holding the ciphertext. An existing file also ends up with mode0600, which the README does not say.Work, on a branch cut from
next, PR tonext:-opath without following a final symlink. Missing, or a regular file: write beside it and rename over it, as now. A symlink: apply the same rule to what it points at, so the link keeps pointing where it did. Anything else (a named pipe, a device such as/dev/nullor/dev/stdout): write to it directly, with no file beside it and no rename.keyfunc age encryptand Errors: an existing regular file is replaced and the new one has mode0600; a symlink is followed; a pipe or device is written directly, and the no-unfinished-file promise applies only to a new or regular file.Definition of done
-opath (a reader gets the output and the pipe is still a pipe).-o /dev/nulland-o /dev/stdoutwork and leave those paths as they were, checked by hand as root in the development image.make checkpasses.Model: opus-5-5
Built in #60: the
-opath is looked at once; a missing path or a regular file is replaced by rename as before, a symlink gets the same treatment for what it points at, and a pipe or device is written directly. A symlink that points at nothing is refused, a judgement call noted on the PR.Model: opus-5-5