Repository navigation
feat: use BRAINTRUST_ prefix for tracing env vars #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Stephen Belanger (Qard)
merged 2 commits into
main
from
abhi/sdk-245-braintrust-env-prefix
Oct 13, 2026
+340
−81
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| //! User-facing environment variables use the `BRAINTRUST_` prefix, matching | ||
| //! `bt` and the Braintrust SDKs. Read them through [`var_os`] so that the | ||
| //! deprecated `BT_` names keep working until support is removed. | ||
|
|
||
| use std::ffi::{OsStr, OsString}; | ||
|
|
||
| /// Pre-`BRAINTRUST_` names of tracing env vars. Each `BT_<NAME>` is still | ||
| /// honored as an alias for `BRAINTRUST_<NAME>` until support is removed in a | ||
| /// major release. Do not add entries: new env vars must use the `BRAINTRUST_` | ||
| /// prefix. | ||
| /// | ||
| /// `BT_TRACE_MANAGED_RUN_ID` and `BT_TRACE_INVOCATION_SETTINGS` are not listed: | ||
| /// they are the internal protocol `bt trace run` uses to configure its child | ||
| /// process tree, read by separately released plugins, and are unchanged. | ||
| pub(crate) const DEPRECATED_BT_ENV_VARS: &[&str] = &[ | ||
| "BT_ANTIGRAVITY_CONFIG_DIR", | ||
| "BT_CURSOR_CONFIG_DIR", | ||
| "BT_CURSOR_PLUGIN_DIR", | ||
| "BT_DAEMON_CONFIG", | ||
| "BT_DAEMON_DATA_DIR", | ||
| "BT_DAEMON_SOCKET", | ||
| "BT_TRACE_OPENCODE_PLUGIN_SPEC", | ||
| "BT_TRACE_PI_PLUGIN_SPEC", | ||
| ]; | ||
|
|
||
| pub(crate) fn canonical_env_name(deprecated: &str) -> String { | ||
| let name = deprecated.strip_prefix("BT_").unwrap_or(deprecated); | ||
| format!("BRAINTRUST_{name}") | ||
| } | ||
|
|
||
| /// The deprecated `BT_` alias of `canonical`, if it has one. | ||
| pub(crate) fn deprecated_env_name(canonical: &str) -> Option<&'static str> { | ||
| DEPRECATED_BT_ENV_VARS | ||
| .iter() | ||
| .copied() | ||
| .find(|deprecated| canonical_env_name(deprecated) == canonical) | ||
| } | ||
|
|
||
| /// Read `canonical`, falling back to its deprecated `BT_` alias. The canonical | ||
| /// name wins when both are set. | ||
| pub(crate) fn var_os(canonical: &str) -> Option<OsString> { | ||
| std::env::var_os(canonical) | ||
| .or_else(|| deprecated_env_name(canonical).and_then(std::env::var_os)) | ||
| } | ||
|
|
||
| pub(crate) fn var(canonical: &str) -> Option<String> { | ||
| var_os(canonical).and_then(|value| value.into_string().ok()) | ||
| } | ||
|
|
||
| /// Pass every resolved value to a child process under both names, so plugins | ||
| /// released before the rename read the same value instead of a stale or | ||
| /// missing deprecated one. | ||
| pub(crate) fn mirror_aliases(command: &mut tokio::process::Command) { | ||
| for deprecated in DEPRECATED_BT_ENV_VARS { | ||
| let canonical = canonical_env_name(deprecated); | ||
| if let Some(value) = var_os(&canonical) { | ||
| command.env(&canonical, &value).env(deprecated, &value); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Set `canonical` and its deprecated alias to the same value on a child | ||
| /// process, so plugins released before the rename still read it. | ||
| pub(crate) fn set_with_alias( | ||
| command: &mut tokio::process::Command, | ||
| canonical: &str, | ||
| value: impl AsRef<OsStr>, | ||
| ) { | ||
| command.env(canonical, value.as_ref()); | ||
| if let Some(deprecated) = deprecated_env_name(canonical) { | ||
| command.env(deprecated, value.as_ref()); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::collections::BTreeSet; | ||
|
|
||
| #[test] | ||
| fn canonical_env_name_swaps_prefix() { | ||
| assert_eq!( | ||
| canonical_env_name("BT_DAEMON_SOCKET"), | ||
| "BRAINTRUST_DAEMON_SOCKET" | ||
| ); | ||
| assert_eq!( | ||
| deprecated_env_name("BRAINTRUST_DAEMON_SOCKET"), | ||
| Some("BT_DAEMON_SOCKET") | ||
| ); | ||
| assert_eq!(deprecated_env_name("BRAINTRUST_API_KEY"), None); | ||
| } | ||
|
|
||
| /// New user-facing env vars must use the `BRAINTRUST_` prefix. Every | ||
| /// `BT_*` name in the crate must be a deprecated alias or internal protocol, | ||
| /// and every deprecated alias must map to a name the crate reads. | ||
| #[test] | ||
| fn env_vars_use_braintrust_prefix() { | ||
| const INTERNAL: &[&str] = &[ | ||
| "BT_TRACE_INVOCATION_SETTINGS", | ||
| "BT_TRACE_MANAGED_RUN_ID", | ||
| // Fallback for `BRAINTRUST_BT_BIN` in the Cursor hook launchers. | ||
| "BT_BIN", | ||
| ]; | ||
| let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); | ||
| let mut sources = String::new(); | ||
| let mut pending = vec![root]; | ||
| while let Some(path) = pending.pop() { | ||
| if path.is_dir() { | ||
| pending.extend(std::fs::read_dir(&path).unwrap().map(|e| e.unwrap().path())); | ||
| } else if path.extension().is_some_and(|ext| ext == "rs") && !path.ends_with("env.rs") { | ||
| sources.push_str(&std::fs::read_to_string(&path).unwrap()); | ||
| } | ||
| } | ||
| let pattern = regex::Regex::new(r"\bBT_[A-Z0-9_]+\b").unwrap(); | ||
| let unexpected: BTreeSet<_> = pattern | ||
| .find_iter(&sources) | ||
| .map(|m| m.as_str()) | ||
| .filter(|name| !DEPRECATED_BT_ENV_VARS.contains(name) && !INTERNAL.contains(name)) | ||
| .collect(); | ||
| assert!( | ||
| unexpected.is_empty(), | ||
| "env vars must use the BRAINTRUST_ prefix: {unexpected:?}" | ||
| ); | ||
| for deprecated in DEPRECATED_BT_ENV_VARS { | ||
| let canonical = canonical_env_name(deprecated); | ||
| assert!( | ||
| sources.contains(&format!("\"{canonical}\"")), | ||
| "{deprecated} maps to {canonical}, which nothing reads" | ||
| ); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a non-default
BRAINTRUST_DAEMON_SOCKETis supplied,isolate_daemonis false, so this block never mirrors the resolved socket intoBT_DAEMON_SOCKET. An already-installed Pi or OpenCode plugin from before the rename reads only the legacy variable and therefore connects to the default or stale socket, while the managed runner flushes the explicitly configured socket, silently losing the run's trace. Mirror an inherited explicit socket into both names on the child just as this block does for an isolated runtime.AGENTS.md reference: AGENTS.md:L53-L58
Useful? React with 👍 / 👎.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed in 7432fa0.
bt trace runnow passes every resolvedBRAINTRUST_*value to the child under both names (env::mirror_aliases), so an explicitBRAINTRUST_DAEMON_SOCKETreaches pre-rename plugins asBT_DAEMON_SOCKETtoo, and a staleBT_DAEMON_SOCKETis overwritten. The isolated-daemon override still applies on top. New teststandalone_run_mirrors_an_explicit_socket_to_the_deprecated_namecovers both the missing and stale cases; it failed before the fix (child saw an emptyBT_DAEMON_SOCKET).