Skip to content

Commit 3db6ab9

Browse files
strawgateclaude
andauthored
Fix generic-OTel apps getting stuck on a write token they already had (#2313)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 2de7b66 commit 3db6ab9

7 files changed

Lines changed: 43 additions & 19 deletions

File tree

logfire/.agents/skills/logfire-instrumentation/SKILL.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ let shutdown_handler = logfire::configure()
112112
.finish()?;
113113
```
114114

115-
Set `LOGFIRE_TOKEN` in your environment or use the Logfire CLI to select a project.
115+
Set `LOGFIRE_TOKEN` in your environment, or don't — the `logfire` crate's `data-dir` feature (on by default) falls back to `.logfire/logfire_credentials.json` when it's unset, same as Python. Set it explicitly only to override that: a different token, or production, where it should be a separately-minted token per [Authenticate and Select the Exact Project](./references/auth.md)'s "If the calling skill needs a write token" section, not the local one.
116116

117117
#### Structured Logging (Rust)
118118

@@ -130,6 +130,12 @@ logfire::info!("Created user {user_id}", user_id = uid);
130130

131131
Always call `shutdown_handler.shutdown()` before program exit to flush data.
132132

133+
### Other Languages (Go, Java, .NET, PHP, Ruby, ...)
134+
135+
No dedicated Logfire SDK — install that language's own OpenTelemetry SDK and point its OTLP exporter at Logfire: [Alternative clients](https://pydantic.dev/docs/logfire/guides/alternative-clients/) has the exact endpoint, protocol (`http/protobuf`, not the gRPC default some exporters ship with), and header format.
136+
137+
For the write token that endpoint needs, see [Authenticate and Select the Exact Project](./references/auth.md)'s "If the calling skill needs a write token" section — for local development, reuse the token `projects use` already put in `.logfire/logfire_credentials.json` rather than assuming a fresh one has to come from the UI.
138+
133139
## Step 4: Set Service Metadata and Metrics
134140

135141
These apply to every language and are what make the **Services**, **Hosts**,

logfire/.agents/skills/logfire-instrumentation/references/auth.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ npx logfire whoami
3636
- Any command failing with `NonInteractiveError` explains what to do next in its own message — usually the exact missing flag (commonly `--org`), but `auth` with no region instead prints a runnable `--region <id> auth` line per region. Follow what the message says and retry once. Don't drop `--non-interactive` to make the error go away; that trades a clear message for the hang it exists to prevent.
3737
- `whoami`'s org/project/region is what every later step must match — instrumentation, verification, any link you give the user. Never substitute a different or "latest" project.
3838
- If both `.logfire/` credentials and `LOGFIRE_TOKEN` are present, `LOGFIRE_TOKEN` wins silently — `whoami` reports whichever is actually in effect. If they'd point at different projects, fix or unset the one you don't want before continuing.
39-
- Never print, log, hard-code, commit, echo, or read a token or its credentials file (`.logfire/logfire_credentials.json`, `~/.logfire/default.toml`) — each just holds a token under a `token` key, so check only whether the file exists, not its contents. A bad or missing credential surfaces as a CLI error, not a prompt.
39+
- Never print, log, hard-code, commit, or echo a token, and don't read `~/.logfire/default.toml`'s contents — a bad or missing credential surfaces as a CLI error, not a prompt. The one exception is reading `.logfire/logfire_credentials.json`'s `token` key programmatically, and only to hand it to a non-native-SDK application language that needs the actual value (see below) — never to print, display, or otherwise surface it.
4040

4141
## If the calling skill needs a write token, not just a CLI session
4242

43-
`logfire-infrastructure`'s Collector exporter and any use of `logfire.experimental.api_client.LogfireAPIClient` need a token minted separately from this CLI session:
43+
Some callers need an actual token value, not just an authenticated CLI session:
4444

45+
- **A setup with no Logfire SDK reading local credentials for it** needs a write token for its OTLP exporter's Authorization header — check the language's own instrumentation reference for whether its SDK already handles this before assuming you need to extract one. When nothing does: for local development, reuse what `projects use` already created instead of minting anything new — read the `token` key from `.logfire/logfire_credentials.json` programmatically and pass it through the runtime's own gitignored local secret mechanism (an environment variable, or a local `.env` the app already loads safely), never printing, logging, or echoing the value itself. For a deployed/production instance, mint a separate write token from **Project Settings → Write tokens** instead and use the platform's own secret manager — a long-lived service shouldn't share the same credential as your CLI session.
4546
- A **write token** for a Collector exporter comes from **Project Settings → Write tokens** in the Logfire UI — the CLI's own credentials authenticate you as a person, not the Collector as a data source.
4647
- An **API key** for `LogfireAPIClient` (hosted-dataset push/pull) comes from **Settings → API Keys**, scoped `project:read_datasets`/`project:write_datasets` — a generic write/read token lacks those scopes.
4748

48-
Same rule applies to both: never print, log, hard-code, commit, or echo the value — inject it via environment variable and check only that it's set, not its value.
49+
Same rule applies to all three: never print, log, hard-code, commit, or echo the value — inject it via environment variable and check only that it's set, not its value.

logfire/.agents/skills/logfire-instrumentation/references/javascript/cloudflare-and-deno.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ For `wrangler.toml`:
2424
compatibility_flags = ["nodejs_compat"]
2525
```
2626

27-
Set local development values in `.dev.vars`:
27+
Set local development values in `.dev.vars`. Workers don't auto-read `.logfire/logfire_credentials.json` the way Python's SDK does, but the token in it is still the one to reuse here rather than minting a new one — see [auth.md](../auth.md)'s "If the calling skill needs a write token" section:
2828

2929
```bash
3030
LOGFIRE_TOKEN=your-write-token
3131
LOGFIRE_ENVIRONMENT=development
3232
```
3333

34-
Store production tokens as Worker secrets:
34+
Store production tokens as Worker secrets, minted separately from **Project Settings → Write tokens** (a long-lived deployed Worker shouldn't share a credential with your local CLI session):
3535

3636
```bash
3737
npx wrangler secret put LOGFIRE_TOKEN
@@ -74,7 +74,7 @@ For Tail Worker flows, use `instrumentTail()` in the producer Worker and `export
7474

7575
## Deno
7676

77-
Deno has built-in OpenTelemetry support. Configure OTLP export to Logfire and use the core package for manual spans:
77+
Deno has built-in OpenTelemetry support. Configure OTLP export to Logfire and use the core package for manual spans. Deno is a generic-OpenTelemetry setup like any non-native-SDK language — for the write token in the header below, reuse what `projects use` already created rather than minting a new one; see [auth.md](../auth.md)'s "If the calling skill needs a write token" section:
7878

7979
```bash
8080
OTEL_DENO=true \

logfire/.agents/skills/logfire-instrumentation/references/javascript/installation-and-env.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Use the package manager detected in [project-detection.md](./project-detection.m
1717

1818
## Environment Variables
1919

20-
Node.js and Cloudflare read Logfire-specific environment values:
20+
Node.js and Cloudflare read Logfire-specific environment values. `LOGFIRE_TOKEN` is optional for local Node.js development specifically: `@pydantic/logfire-node` falls back to `.logfire/logfire_credentials.json` when it's unset, same as the Python SDK. Cloudflare has no equivalent fallback (see [cloudflare-and-deno.md](./cloudflare-and-deno.md)), so set it explicitly there always; for Node.js, set it explicitly only to override the local one — a different token, or production, where it should be a separately-minted token per [auth.md](../auth.md)'s "If the calling skill needs a write token" section.
2121

2222
```bash
2323
LOGFIRE_TOKEN=your-write-token

logfire/.agents/skills/logfire-setup-offline.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ nothing below needs a network fetch to resolve.
77

88
A pointer to "the `logfire-infrastructure` skill" (or any other skill named
99
above) means the section below headed `# Skill: logfire-infrastructure` --
10-
read it in place of fetching it. This build omits the `./references/...` deep-dive files (language-specific edge cases) to stay shorter, EXCEPT the authenticate reference every skill's Step 1 depends on -- that one is always included below, not just linked to -- so if a pointer to any other `./references/...` file turns out to matter, fetch it directly from the repo instead.
10+
read it in place of fetching it. This build omits the `./references/...` deep-dive files (language-specific edge cases) to stay shorter, EXCEPT the authenticate reference every skill's Step 1 depends on -- that one is always included below, not just linked to -- so if a pointer to any other `./references/...` or `../<skill-name>/references/...` file turns out to matter, fetch it directly from the repo instead.
1111

1212

1313
---
@@ -50,7 +50,7 @@ Read `AGENTS.md`/`CLAUDE.md`/`README.md` and skim the language, runtime, and pac
5050

5151
Fetch the skill(s) identified in Step 2 now, for the actual install/instrument/verify steps. Each one's own authenticate step still runs its own `whoami` check first — that's what confirms it's the same project and region resolved here, not an assumption carried over — and only then skips the rest of its auth commands. They're independently fetchable on purpose, so this composes whether someone reaches a specific skill through this hub or on its own.
5252

53-
Never print, log, hard-code, commit, or echo a token or its credentials file, in any of these skills, at any point.
53+
Never print, log, hard-code, commit, or echo a token, in any of these skills, at any point. The one exception — reading `.logfire/logfire_credentials.json`'s `token` key programmatically to hand a non-native-SDK application its write token, never to display it — is in [auth.md](../logfire-instrumentation/references/auth.md#if-the-calling-skill-needs-a-write-token-not-just-a-cli-session).
5454

5555
---
5656

@@ -167,7 +167,7 @@ let shutdown_handler = logfire::configure()
167167
.finish()?;
168168
```
169169

170-
Set `LOGFIRE_TOKEN` in your environment or use the Logfire CLI to select a project.
170+
Set `LOGFIRE_TOKEN` in your environment, or don't — the `logfire` crate's `data-dir` feature (on by default) falls back to `.logfire/logfire_credentials.json` when it's unset, same as Python. Set it explicitly only to override that: a different token, or production, where it should be a separately-minted token per [Authenticate and Select the Exact Project](./references/auth.md)'s "If the calling skill needs a write token" section, not the local one.
171171

172172
#### Structured Logging (Rust)
173173

@@ -185,6 +185,12 @@ logfire::info!("Created user {user_id}", user_id = uid);
185185

186186
Always call `shutdown_handler.shutdown()` before program exit to flush data.
187187

188+
### Other Languages (Go, Java, .NET, PHP, Ruby, ...)
189+
190+
No dedicated Logfire SDK — install that language's own OpenTelemetry SDK and point its OTLP exporter at Logfire: [Alternative clients](https://pydantic.dev/docs/logfire/guides/alternative-clients/) has the exact endpoint, protocol (`http/protobuf`, not the gRPC default some exporters ship with), and header format.
191+
192+
For the write token that endpoint needs, see [Authenticate and Select the Exact Project](./references/auth.md)'s "If the calling skill needs a write token" section — for local development, reuse the token `projects use` already put in `.logfire/logfire_credentials.json` rather than assuming a fresh one has to come from the UI.
193+
188194
## Step 4: Set Service Metadata and Metrics
189195

190196
These apply to every language and are what make the **Services**, **Hosts**,
@@ -532,13 +538,14 @@ npx logfire whoami
532538
- Any command failing with `NonInteractiveError` explains what to do next in its own message — usually the exact missing flag (commonly `--org`), but `auth` with no region instead prints a runnable `--region <id> auth` line per region. Follow what the message says and retry once. Don't drop `--non-interactive` to make the error go away; that trades a clear message for the hang it exists to prevent.
533539
- `whoami`'s org/project/region is what every later step must match — instrumentation, verification, any link you give the user. Never substitute a different or "latest" project.
534540
- If both `.logfire/` credentials and `LOGFIRE_TOKEN` are present, `LOGFIRE_TOKEN` wins silently — `whoami` reports whichever is actually in effect. If they'd point at different projects, fix or unset the one you don't want before continuing.
535-
- Never print, log, hard-code, commit, echo, or read a token or its credentials file (`.logfire/logfire_credentials.json`, `~/.logfire/default.toml`) — each just holds a token under a `token` key, so check only whether the file exists, not its contents. A bad or missing credential surfaces as a CLI error, not a prompt.
541+
- Never print, log, hard-code, commit, or echo a token, and don't read `~/.logfire/default.toml`'s contents — a bad or missing credential surfaces as a CLI error, not a prompt. The one exception is reading `.logfire/logfire_credentials.json`'s `token` key programmatically, and only to hand it to a non-native-SDK application language that needs the actual value (see below) — never to print, display, or otherwise surface it.
536542

537543
## If the calling skill needs a write token, not just a CLI session
538544

539-
`logfire-infrastructure`'s Collector exporter and any use of `logfire.experimental.api_client.LogfireAPIClient` need a token minted separately from this CLI session:
545+
Some callers need an actual token value, not just an authenticated CLI session:
540546

547+
- **A setup with no Logfire SDK reading local credentials for it** needs a write token for its OTLP exporter's Authorization header — check the language's own instrumentation reference for whether its SDK already handles this before assuming you need to extract one. When nothing does: for local development, reuse what `projects use` already created instead of minting anything new — read the `token` key from `.logfire/logfire_credentials.json` programmatically and pass it through the runtime's own gitignored local secret mechanism (an environment variable, or a local `.env` the app already loads safely), never printing, logging, or echoing the value itself. For a deployed/production instance, mint a separate write token from **Project Settings → Write tokens** instead and use the platform's own secret manager — a long-lived service shouldn't share the same credential as your CLI session.
541548
- A **write token** for a Collector exporter comes from **Project Settings → Write tokens** in the Logfire UI — the CLI's own credentials authenticate you as a person, not the Collector as a data source.
542549
- An **API key** for `LogfireAPIClient` (hosted-dataset push/pull) comes from **Settings → API Keys**, scoped `project:read_datasets`/`project:write_datasets` — a generic write/read token lacks those scopes.
543550

544-
Same rule applies to both: never print, log, hard-code, commit, or echo the value — inject it via environment variable and check only that it's set, not its value.
551+
Same rule applies to all three: never print, log, hard-code, commit, or echo the value — inject it via environment variable and check only that it's set, not its value.

logfire/.agents/skills/logfire-setup/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ Read `AGENTS.md`/`CLAUDE.md`/`README.md` and skim the language, runtime, and pac
3737

3838
Fetch the skill(s) identified in Step 2 now, for the actual install/instrument/verify steps. Each one's own authenticate step still runs its own `whoami` check first — that's what confirms it's the same project and region resolved here, not an assumption carried over — and only then skips the rest of its auth commands. They're independently fetchable on purpose, so this composes whether someone reaches a specific skill through this hub or on its own.
3939

40-
Never print, log, hard-code, commit, or echo a token or its credentials file, in any of these skills, at any point.
40+
Never print, log, hard-code, commit, or echo a token, in any of these skills, at any point. The one exception — reading `.logfire/logfire_credentials.json`'s `token` key programmatically to hand a non-native-SDK application its write token, never to display it — is in [auth.md](../logfire-instrumentation/references/auth.md#if-the-calling-skill-needs-a-write-token-not-just-a-cli-session).

scripts/build_offline_skill_prompt.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,25 @@ def _render_appendix(name: str, *, only_mandatory: bool = False) -> str:
104104

105105

106106
def _preamble(*, include_references: bool) -> str:
107+
# Every skill body links to its own and other skills' references by relative path
108+
# (`./references/...`, `../logfire-x/references/...`) -- correct for the individual
109+
# SKILL.md files these bodies are copied from verbatim, but not for this flattened
110+
# bundle, which sits one directory above every skill it concatenates. Rather than
111+
# rewrite every link (risking a subtly wrong path that's harder to catch than an
112+
# unrewritten one), this note tells the reader the resolution rule directly: strip the
113+
# relative prefix, keep the skill-qualified remainder, and match it against an
114+
# appendix heading below -- never follow either link shape as a literal path.
107115
references_note = (
108-
' A pointer to a file under `./references/...` means the matching entry in the '
109-
'**Reference Files** appendix at the end, headed with that same path.'
116+
' A pointer to a file under `./references/...` (same skill) or '
117+
'`../<skill-name>/references/...` (a different skill) means the matching entry in '
118+
'the **Reference Files** appendix at the end, headed with that skill-qualified '
119+
"path -- never follow either as a literal path from this file's own location."
110120
if include_references
111121
else ' This build omits the `./references/...` deep-dive files (language-specific '
112122
"edge cases) to stay shorter, EXCEPT the authenticate reference every skill's "
113123
'Step 1 depends on -- that one is always included below, not just linked to -- so '
114-
'if a pointer to any other `./references/...` file turns out to matter, fetch it '
115-
'directly from the repo instead.'
124+
'if a pointer to any other `./references/...` or `../<skill-name>/references/...` '
125+
'file turns out to matter, fetch it directly from the repo instead.'
116126
)
117127
return (
118128
'# Pydantic Logfire — Offline Setup Prompt\n\n'

0 commit comments

Comments
 (0)