Skip to content

Resolve output paths against the client's cwd, not the daemon's - #34

Open
dkulp wants to merge 1 commit into
VKCOM:masterfrom
FalconChristmas:fix-client-cwd
Open

Resolve output paths against the client's cwd, not the daemon's#34
dkulp wants to merge 1 commit into
VKCOM:masterfrom
FalconChristmas:fix-client-cwd

Conversation

@dkulp

@dkulp dkulp commented Aug 19, 2026

Copy link
Copy Markdown

The bug

nocc-daemon is a long-lived, machine-wide process on a fixed socket. It is started by whichever nocc invocation first finds no daemon running, inherits that process's working directory, and then serves every later invocation on the machine — from any directory — until it idles out.

Relative paths in a request were resolved against that inherited cwd rather than the cwd the client sends alongside the command line:

  • the .o itself — files-receiving.go wrote invocation.objOutFile verbatim
  • the .d file name
  • the header paths inside the .d, which were relativized with os.Getwd()
  • the .nocc-pch path, plus OrigHFile/OrigPchFile, which the server maps into its per-pch root dir by concatenating them onto rootDir and so need to be absolute the way the dep includes in that struct already are

Reproducing

Build one project, then build a second from a different directory while the daemon is still alive:

mkdir -p a/sub b/sub && echo 'int fa(){return 1;}' > a/fa.cpp && echo 'int fb(){return 2;}' > b/fb.cpp
(cd a && nocc g++ -c fa.cpp -o sub/fa.o)   # starts the daemon, rooted at .../a
(cd b && nocc g++ -c fb.cpp -o sub/fb.o)   # exit 0, no output
ls a/sub b/sub

fb.o lands in a/sub/. b/sub/ is empty. Exit code 0, nothing logged.

When the relative path does not happen to exist under the daemon's cwd, it is at least loud — the daemon logs

compiling locally: open src/foo.o: no such file or directory

and falls back to the local compiler. Correct output, but with no distribution at all. A project whose Makefile writes objects into a subdirectory (-o src/foo.o) hits this whenever the daemon happens to be rooted somewhere without a matching subdirectory.

The fix

Store the request cwd on Invocation and add GetObjOutFileAbs(), mirroring the existing GetCppInFileAbs(). -I / -iquote / -isystem / -include and -MF were already pre-resolved this way at parse time; -o was the one that was not.

The default -MT target and the input file recorded in the .d are deliberately left as spelled on the command line — that is what the compiler writes, and what make matches its rule targets against.

Test

Three unit tests in internal/client/invocation_test.go covering resolution against the request cwd, the as-specified string surviving for the depfile target, two cwds not colliding on one output path, and the depfile name.

No effect on the tests/ suite: EmulateDaemonInsideThisProcessForDev fills the request cwd from the test process's own os.Getwd(), so there the two were already the same directory. Same results as master on my machine.

nocc-daemon is a long-lived, machine-wide process on a fixed socket. It is
started by whichever `nocc` invocation first finds no daemon running, inherits
that process's working directory, and then serves every later invocation on the
machine -- from any directory -- until it idles out.

Relative paths in a request were resolved against that inherited cwd rather than
the cwd the client sends alongside the command line:

  * the .o itself (files-receiving.go wrote invocation.objOutFile verbatim),
  * the .d file name,
  * the header paths inside the .d, which were relativized with os.Getwd(),
  * the .nocc-pch path, plus OrigHFile/OrigPchFile, which the server maps into
    its per-pch root dir by concatenating them onto rootDir and so need to be
    absolute the way the dep includes in that struct already are.

Build one project, then build a second from a different directory while the
daemon is still alive, and the second build's objects land in the first one's
tree. When the relative path happens to exist there this is silent: the compile
exits 0, prints nothing, and make simply does not find its output. When it does
not exist, the daemon logs "compiling locally: open <path>: no such file or
directory" and falls back to the local compiler -- correct output, but with no
distribution at all. A project whose Makefile writes objects into a
subdirectory (-o src/foo.o) hits this whenever the daemon happens to be rooted
somewhere without a matching subdirectory.

Store the request cwd on Invocation and add GetObjOutFileAbs(), mirroring the
existing GetCppInFileAbs(). -I/-iquote/-isystem/-include and -MF were already
pre-resolved this way at parse time; -o was the one that was not.

The default -MT target and the input file recorded in the .d are deliberately
left as spelled on the command line -- that is what the compiler writes, and
what make matches its rule targets against.

No effect on the tests/ suite: EmulateDaemonInsideThisProcessForDev fills the
request cwd from the test process's own os.Getwd(), so there the two were
already the same directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 20:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a correctness bug in nocc-daemon where relative output/dependency/PCH-related paths were effectively resolved against the daemon’s inherited working directory (from whichever client started it) instead of the requesting client’s working directory, causing outputs to be written to the wrong tree (sometimes silently).

Changes:

  • Persist the requesting client’s cwd on Invocation and introduce GetObjOutFileAbs() to resolve -o against that cwd.
  • Switch object receive/write, depfile naming + header-path relativization, and own-PCH path fields to use the request cwd rather than the daemon process cwd.
  • Add unit tests ensuring -o and depfile naming resolve against the request cwd while keeping depfile target strings as-specified.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/client/pch-generation.go Uses absolute object/PCH paths derived from the client request cwd for own-PCH generation.
internal/client/invocation.go Stores request cwd on Invocation, hardens pathAbs, and adds GetObjOutFileAbs().
internal/client/invocation_test.go Adds unit tests covering object output path resolution and depfile naming behavior.
internal/client/files-receiving.go Writes received .o to the absolute output path resolved from the request cwd.
internal/client/dep-cmd-flags.go Generates depfile name and depfile header paths relative to the client request cwd (not daemon cwd).
internal/client/daemon.go Updates logging to report the absolute output path for PCH fallback behavior.
internal/client/compile-remotely.go Updates logging to report the absolute output path for saved .o.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants