Skip to content

[FOU-588] Build the Go module graph of a go.work workspace - #843

Merged
abhisek merged 2 commits into
claude/go-module-graphfrom
claude/go-work-graph
Oct 24, 2026
Merged

abhisek merged 2 commits into
claude/go-module-graphfrom
claude/go-work-graph

Conversation

@abhisek

@abhisek abhisek commented Oct 24, 2026

Copy link
Copy Markdown
Member

What was wrong

vet built no module graph for a module that a go.work workspace uses. The go command builds such a module with the build list of the workspace, which no single go.mod holds. So each Go finding of a workspace stayed unknown · no graph. vet itself is such a workspace.

What this changes

  • The build list of the workspace. vet passes the go.work that it finds to modgraph.Discover (feat(modgraph): the build list of a go.work workspace code#56), which gives that build list.
  • How vet finds the go.work. It reads GOWORK, and else searches the parents of the module directory, as the go command does. A relative GOWORK gives no graph, because the go command refuses it.
  • Modules that pass their imports on. These are no package of this go.mod, so an import of them passes on the imports of their code:
    • another module of the workspace, which is local code
    • a module that only the workspace adds
  • The unmapped-module guard. It now applies only to a module that this go.mod requires. Such a module that maps to no package still gives no graph.
  • When there is no graph. Discover fails, and vet keeps no graph, when:
    • a go.mod that the build list needs is not on the disk
    • a workspace module is outside the target
    • a go.mod has an exclude directive
  • Docs. docs/output.md describes workspaces, and the glossary entry of the dependency graph names them.
  • Acceptance. The script reach/import/go-workspace-graph and its catalog row are new.

Field run

Go vulnerability findings of the root go.mod:

Project Before After
vet (a workspace of . and ./scripts) 1 imported, 12 unknown (no graph) 13 imported
safedep/code 15 imported no change
control-tower 68 imported, 35 unknown (no dependency source) no change

Known gap

A finding gives the version that its go.mod requires. The workspace can build a higher version, and the reach then describes the code of that version. docs/output.md says so.

Tests

  • codeusage:
    • a workspace
    • another workspace module that passes its imports on
    • a workspace whose build list is not on the disk
    • GOWORK with a path, through a link, and relative
    • a workspace above the scan root
    • a module that go.mod does not require, which passes its imports on
  • Acceptance: reach/import/go-workspace-graph covers an import through another workspace module, an import through a module of the module cache, and unused. It fails on the base branch.
  • make check passes.

Order

This pull request stacks on #842 and targets its branch. safedep/code is pinned to the head of safedep/code#56. Once both merge, I retarget this pull request to v2 and pin safedep/code to main.


Generated by Claude Code

vet built no module graph for a module that a go.work workspace uses,
because the go command builds such a module with the build list of the
workspace, and no single go.mod holds it. So each Go finding of a
workspace stayed unknown with no graph. vet itself is such a workspace.

vet now passes the go.work that it finds to modgraph, which gives the
build list of the workspace. Another module of the workspace is local
code, so it passes its imports on. A module that only the workspace adds
is no package of this go.mod, and it passes its imports on too. A
reached module that go.mod requires and that maps to no package still
gives no graph. A relative GOWORK gives no graph, because the go command
refuses it.

safedep/code is pinned to the head of its branch claude/go-work-graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm
A review of safedep/code#56 found six inputs where the build list of a
workspace differed from the go command. The fixed head follows
readModGraph and the vendor rules of the go command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm
@safedep

safedep Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

Package Details
Package Malware Vulnerability Risky License Report
icon github.com/safedep/code @ v0.0.0-20261024083405-d58281884ca1
go.mod
ok icon
ok icon
ok icon
🔗

View complete scan results →

This report is generated by SafeDep GitHub App

@coderabbitai

coderabbitai Bot commented Oct 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: QUIET
  • Plan: Advanced
  • Run ID: a8f1597f-9c54-4f54-b197-6ee1890549b5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T08:39:45.627264Z ca3919e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@abhisek
abhisek merged commit b39c46b into claude/go-module-graph Oct 24, 2026
3 checks passed
@abhisek
abhisek deleted the claude/go-work-graph branch October 24, 2026 10:21
abhisek added a commit that referenced this pull request Oct 24, 2026
…pace (#842)

* [FOU-586] Read the Go module graph of a go.mod from the code on the disk

A go.mod lists the modules of a build, but not which module imports
which. So each Go vulnerability finding of a module that the code does
not import stayed unknown with the cause no-graph. vet could not say
unused or imported via X, and could not prove not-called.

The codeusage enricher now builds the graph of each go.mod with
safedep/code modgraph. modgraph reads the import blocks of the Go files
of the main module and of each module of the build list in vendor, in a
local replacement inside the target or in the module cache. It runs no
go command and downloads nothing. The engine asks a new
plugin.Grapher for the graph of a manifest with none, during the
extraction, and stores it with the manifest.

A module with no source on the disk is open: it may import any module.
model.Graph keeps the open nodes, and the scan file stores them. The
reach rules read them: a path of known edges gives imported via X, a
path that only an open module gives is unknown with the cause
no-dependency-source, and no path is unused. One diagnostic counts the
open modules and says to run go mod download.

Scalibr lists the target of a replace directive in place of the module.
The go.mod extractor now records the module that it replaces, as
Package.Replaces. The code usage and the graph map a replaced module by
it, so its imports count.

vet builds no graph for a module that a go.work uses, or for a go.mod
below go 1.17. Its findings keep no-graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-586] Take imports from the roots of the Go module graph

A review of the Go module graph found false unused results. The reach
rules took the imported packages from the code usage, which covers the
whole target and skips the build, target and dist directories. So a
module that only a skipped directory imports read as unused. A module
that only another go.mod of the target imports gave paths into this
go.mod. The standard library has no import path, so it read as unused.

The module graph of a go.mod now marks itself as a graph of imports, and
the scan file keeps the mark. For such a graph the reach rules take the
imported packages from its roots, which are the imports of the code of
that module. The standard library is always imported.

A reached module that maps to no package of the manifest hid its
imports, for example when two replace directives name one target. Now
that module gives no graph. vet also reads GOWORK as the go command
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-586] Resolve links in GOWORK and refuse a relative GOWORK

A review found two inputs that gave a module graph for a module of a
workspace. vet compared a use path in the spelling of GOWORK with the
canonical scan root, so a GOWORK through a link never matched the module.
On macOS each target under /tmp or /var is such a link. vet also read a
relative GOWORK from its own directory, but the go command refuses a
relative GOWORK.

vet now resolves the links of both paths before it compares them. A
relative GOWORK counts as a workspace that holds the module, so the
module gets no graph. The docs say that vet does not read a GOWORK
that go env -w sets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-586] Find a go.work above the scan root and bump codeusage

The search for a go.work stopped at the scan root. The go command
searches each parent directory, so a scan of one module of a workspace
missed the workspace and built a graph from go.mod alone. That graph can
give a false unused. The search now goes up to the root of the file
system. A go.work outside the scan root is named by its full path.

The mapping of a replaced Go module changed the data of the codeusage
enricher, so its Version goes from 11 to 12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-586] docs: Say that the module graph runs no build

The doc comment of Graph said that it runs no go command. To find the
module cache, vet can run go env once for each process. The comment now
says that the graph runs no build and downloads nothing, as
docs/output.md says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-586] Pin safedep/code to the head of main

safedep/code#54 merged, so vet no longer needs the head of its branch.
The pin moves to the head of main, which holds the same tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-588] Build the Go module graph of a go.work workspace (#843)

* [FOU-588] Build the Go module graph of a go.work workspace

vet built no module graph for a module that a go.work workspace uses,
because the go command builds such a module with the build list of the
workspace, and no single go.mod holds it. So each Go finding of a
workspace stayed unknown with no graph. vet itself is such a workspace.

vet now passes the go.work that it finds to modgraph, which gives the
build list of the workspace. Another module of the workspace is local
code, so it passes its imports on. A module that only the workspace adds
is no package of this go.mod, and it passes its imports on too. A
reached module that go.mod requires and that maps to no package still
gives no graph. A relative GOWORK gives no graph, because the go command
refuses it.

safedep/code is pinned to the head of its branch claude/go-work-graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-588] Pin safedep/code to the fixed head of claude/go-work-graph

A review of safedep/code#56 found six inputs where the build list of a
workspace differed from the go command. The fixed head follows
readModGraph and the vendor rules of the go command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

---------

Co-authored-by: Claude <noreply@anthropic.com>

* [FOU-588] Pin safedep/code to the head of main

safedep/code#56 merged, so vet no longer needs the head of its branch.
The head of main holds the same tree as the reviewed branch head,
fe48e4f, which also makes the paths of Discover absolute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

* [FOU-588] Add an acceptance script for a workspace with no graph

docs/output.md says that vet builds no module graph for a workspace
whose build list it cannot know, and that its findings keep unknown with
the cause no-graph. No acceptance script held that guarantee. The new
script covers a go.mod of the build list that is not on the disk, a
workspace module outside the target, and an exclude directive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQbqPqd9xn1fT6hBsyoUm

---------

Co-authored-by: Claude <noreply@anthropic.com>
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