Skip to content

[Nexthop] [fboss2] don't stage a config session from read-only commands - #1474

Open
hillol-nexthop wants to merge 1 commit into
facebook:mainfrom
nexthop-ai:don-t-stage-config-session-from-read
Open

[Nexthop] [fboss2] don't stage a config session from read-only commands#1474
hillol-nexthop wants to merge 1 commit into
facebook:mainfrom
nexthop-ai:don-t-stage-config-session-from-read

Conversation

@hillol-nexthop

@hillol-nexthop hillol-nexthop commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Pre-submission checklist

  • I've ran the linters locally and fixed lint errors related to the files I modified in this PR. You can install the linters by running pip install -r requirements-dev.txt && pre-commit install
  • pre-commit run

Slack thread

Summary

fboss2-dev config history and fboss2-dev config session diff are purely informational, but both create a config session as a side effect.

Both call ConfigSession::getInstance(), which lazily default-constructs the singleton, whose constructor unconditionally runs initializeSession(). With nothing staged, that takes the "starting a new session" branch: ensureDirectoryExists(~/.fboss2), copySystemConfigToSession() (seeding ~/.fboss2/agent.conf from the live /etc/coop/agent.conf), base_ = git HEAD, and saveMetadata() (writing ~/.fboss2/cli_metadata.json).

Consequences:

  • Running config history on a clean box silently starts a config session. config session diff/commit afterwards behave as if the user has an edit in flight.
  • session diff's own "No config session exists. Make a config change first." message is checked after getInstance() has already created the session — the message is false by the time it is printed.

Fix

Add a ConfigSession::SessionInit { CreateIfAbsent, ReadOnly } mode, threaded through the constructors into initializeSession():

  • ReadOnly returns before the "start a new session" branch, so nothing under ~/.fboss2 is written. The git handle, path getters, configDomains() and readStagedContent() all still work, which is everything these two commands actually use.
  • CreateIfAbsent remains the default, so every editing command is byte-for-byte unchanged.
  • loadConfig() — the entry point of every editing command — now calls initializeSession(CreateIfAbsent) explicitly, so even a ReadOnly-constructed singleton materializes a session the moment someone genuinely reads the agent config.
  • Call sites: CmdConfigHistory and CmdConfigSessionDiff pass SessionInit::ReadOnly.

Two deliberate choices worth reviewing:

  1. initializeGit() still runs in ReadOnly mode. It only bootstraps the system config repo under /etc/coop (never ~/.fboss2), it is idempotent once the repo exists, and both read commands need a usable repo to report anything at all. Skipping it would make history fail with a raw git error on a fresh device instead of showing the baseline commit. Happy to tighten this further if reviewers prefer.
  2. ~/.fboss2 is now ensured in saveConfig() and saveMetadata() rather than only at construction, since the directory is no longer guaranteed to have been created up front. This also removes a pre-existing latent failure in the rollback() path, which calls saveMetadata() unconditionally.

A "delete the files on exit" approach was considered and rejected: it races against a concurrent config session start/edit in another terminal, and does a pointless copy-then-delete on every invocation. Not creating the file at all is strictly better.

Test Plan

New unit tests (all use a new CmdConfigTestBase::setupReadOnlyTestableConfigSession() helper that installs a SessionInit::ReadOnly session, mirroring how the commands construct theirs):

CmdConfigHistoryTest.cpp

  • historyDoesNotCreateSessionFiles — asserts ~/.fboss2 does not exist beforehand, runs the command, asserts the output still lists Initial commit, and that agent.conf, cli_metadata.json, bgp_config.json and hasActiveSession() are all still absent/false.
  • historyLeavesExistingSessionUntouched — with a session already staged, the command still works and the staged content is byte-identical afterwards.
  • defaultSessionStillCreatesSessionFile — regression guard that the default CreateIfAbsent editing path still seeds ~/.fboss2/agent.conf.

CmdConfigSessionDiffTest.cpp

  • diffNoSessionDoesNotCreateSessionFiles — the "No config session exists" message is now true when printed; no session files created.
  • diffTwoRevisionsDoesNotCreateSessionFiles — the revision-vs-revision mode never consults the session, and no longer stages one.
  • diffReadOnlyStillSeesStagedSession — a real staged session is still diffed correctly and left untouched.

Existing CmdConfigHistoryTest/CmdConfigSessionDiffTest/CmdConfigSessionTest/ConfigSessionSystemdTest cases exercise the unchanged CreateIfAbsent path.

Signed-off-by: vybhav-nexthop <vybhav@nexthop.ai>
@hillol-nexthop
hillol-nexthop requested a review from a team as a code owner August 6, 2026 09:13
@meta-cla meta-cla Bot added the CLA Signed label Aug 6, 2026
@meta-codesync

meta-codesync Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D115030926. (Because this pull request was imported automatically, there will not be any future comments.)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant