feat(claudecode): estimate session cost from token usage - #2379
Draft
entire[bot] wants to merge 31 commits into
Draft
feat(claudecode): estimate session cost from token usage#2379entire[bot] wants to merge 31 commits into
entire[bot] wants to merge 31 commits into
Conversation
Versioned per-provider rate tables (USD per MTok) embedded via go:embed, with exact-id then alias-glob lookup and no implicit family fallback: an unknown model yields no estimate rather than a guessed price. Cache rates default to the Anthropic convention (read 0.1x, write 1.25x of input) unless a model sets explicit rates. Users can replace or extend entries and disable estimation entirely through a new settings.pricing block.
…oint metadata TokenUsage gains optional cost_usd/cost_source (reported|estimated|mixed) carried through every accumulation path including subagent totals; nil means unknown, never $0. A new ModelUsageCalculator capability lets agents attribute usage to the model that produced it (model can change mid-session, so session-level attribution is not enough); Claude Code implements it with streaming dedup keeping the model on the kept row. A centralized dispatcher prices each per-model bucket once, adds a remainder bucket when flat totals exceed bucket coverage, and folds honest provenance (mixed when any tokens stay unpriced). Buckets persist as model_usage[] on session metadata and aggregate onto the checkpoint summary, so downstream consumers can ingest per-model cost without re-deriving it.
…mmands Renders cost_usd with its provenance suffix, e.g. "Cost: $0.23 (estimated)"; absent cost renders nothing rather than $0.00. checkpoint tokens --compare adds a cost delta only when both sides carry cost, and tokens profile reports coverage as "across N of M checkpoints". Display never computes cost; it renders what the lifecycle stored.
Extend the embedded Anthropic pricing table so any Claude model id resolves to a rate, regardless of how the caller spells it. Models: add the current lineup and the legacy tiers that older transcripts still reference — mythos-5, opus 4.5/4.1/4.0, opus 3 (claude-3-opus), sonnet 4.5/4.0, 3.7/3.5/3 sonnet, and 3.5/3 haiku — alongside the existing entries, and correct the opus 4.7/4.6 effective dates. Rates and cache convention are unchanged (cache read/write are derived at 0.1x/1.25x of input). Aliases: every entry now carries the id spellings seen in the wild — Anthropic dated ids (<id>-2*), slash-prefixed ids (anthropic/<id>), Bedrock ids and regional inference profiles (anthropic.<id>[-*], us|eu|apac.anthropic.<id>*, which also cover legacy dated -vN:0 forms), and Vertex ids (<id>@*). Long-context [1m] fix: Claude Code emits ids like "claude-fable-5[1m]", but path.Match reads a literal "[1m]" alias as a character class, so it would never match. Rather than add a broken alias, Lookup now strips a trailing "[...]" suffix from the query before matching, so the id resolves to its base model and bills at the base rate (1M-context requests carry no long-context premium on current-generation models). doc.go records that behavior plus two known under-estimates: 1-hour-TTL cache writes (bill 2x input vs the 1.25x 5-minute default, pending per-TTL bucket parsing) and fast-mode turns (usage.speed="fast", a premium not published in-table). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ex legacy ids A resolution sweep over real-world model-id spellings surfaced three forms the alias set missed: Bedrock global inference profiles (global.anthropic.<id>-v1:0), slash-prefixed dated ids (anthropic/<id>-<date>), and Vertex legacy versioned ids (<id>-v2@<date>). Each entry now carries alias globs for all three; the slash alias became a glob so dated variants resolve too.
model_usage entries persist full TokenUsage values, so the remainder bucket must account for api_call_count too — otherwise per-model buckets don't sum to the flat total on that field whenever a shortfall bucket is created.
…nsation Both extractSessionData and the Copilot session-wide backfill passed an empty fallbackModel to tokenUsageWithCost even though state.ModelName is in scope at their call sites (the live-transcript sibling already threads it). That left the Copilot CLI backfill permanently unpriced and degraded Claude Code checkpoints to a mixed cost source whenever subagent usage produced a remainder bucket. Both paths now receive the session model.
…pic cache rates Table.add now canonicalizes the index key (trimmed, lower-cased) and stores the trimmed id, so an override whose id differs only in case or whitespace replaces the builtin entry instead of appending a phantom duplicate. An alias-less override inherits the replaced entry's aliases, keeping the dated and provider-prefixed spellings resolvable. Lookup's fast path probes the index with the same canonicalization. Estimate's nil-cache-rate fallbacks are now provider-aware: the Anthropic 0.1x/1.25x multipliers apply only to anthropic-provider rates; any other provider missing an explicit cache rate falls back to the full input rate (1.0x) rather than silently undercharging. The openai and google tables now set cache_write_per_mtok explicitly. doc.go documents the Anthropic-only scope. validateRate rejects empty/whitespace aliases and malformed glob aliases (path.Match ErrBadPattern) so a mistyped override alias fails loudly at load instead of silently never matching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merging a priced usage with an unpriced-but-token-bearing usage now folds to CostSourceMixed, mirroring foldBucketCost's partial-coverage rule so an aggregate that combines a costed session with an uncosted token-bearing one no longer masquerades as fully priced. Adds one exported helper, types.MergeCostSourceUsages(a, b), which behaves like MergeCostSource over the two usages' (source, cost) pairs but additionally returns mixed when exactly one side is priced and the other carries nonzero billable tokens with no cost. Applied at the three scalar merge sites: accumulateTokenUsage (strategy), aggregateTokenUsage (checkpoint), and addCheckpointTokenUsage (cli). The in-place accumulate keeps computing the source before the cost and token fields are summed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
remainderBucket now flattens the flat total's SubagentTokens subtree (at arbitrary depth) before computing the per-field shortfall against the per-model buckets. Real extractors report main-transcript totals in the scalar fields and subagent totals in a SubagentTokens subtree that CalculateModelUsage never sees, so previously the subagent tokens went entirely unpriced — a Claude Code session with 100k main + 500k subagent tokens priced only the 100k. The subagent shortfall now lands in a remainder bucket priced under the fallback model. bucketTokens drops the SubagentTokens subtree so a fallback bucket carries main-scoped scalar counts only: the remainder bucket already covers the subtree, and keeping it on the fallback bucket would both double-count downstream and alias the caller's subtree. PriceUsage builds its single bucket the same way (nil subtree, cost preserved) so a reported-cost usage is untouched while its subagent pointer is no longer shared. accumulateTokenUsage's existing==nil branch deep-copies SubagentTokens so two aggregates that fold the same step cannot cross-contaminate each other's subagent counts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… them
A minimal price override ({id, input, output}) inherited aliases but not
the provider, so the provider-gated cache multipliers stopped applying and
cache-read tokens billed at the full input rate (10x overcharge on
Anthropic models). Overrides now inherit provider and explicit cache-rate
pointers from the replaced entry, mirroring alias inheritance.
Two condensation-time cost-attribution fixes.
Fix 1 (reprice-after-backfill): CondenseSession priced the extracted
session data before backfilling the model from the transcript. For agents
whose model only comes from the transcript (Pi implements ModelExtractor;
state.ModelName can be "" at condensation, e.g. a mid-turn commit before
agent_end), the pricing pass ran with an empty fallbackModel, so the flat
usage came out unpriced under an empty-model bucket while the persisted
checkpoint recorded Model="gpt-5.5" with a nil cost and ModelUsage=[{Model:"",…}].
After the model is recovered, re-price the SAME extracted transcript slice
with the recovered model (both extraction paths priced from
state.CheckpointTranscriptStart with an empty subagents dir, so the reprice
is equivalent) and swap in the priced usage and real-model buckets. Gated by
sessionDataUnpricedFromTranscript so the reprice only fires when the
transcript actually yielded unpriced usage, leaving the state.ModelUsage
mirror fallback intact.
Fix 2 (skip zero-token buckets): priceBuckets estimated buckets with no
billable tokens, producing a $0.00 "estimated" cost. Add a bucketHasTokens
guard so zero-usage buckets stay unpriced (nil cost, empty source),
consistent with the ModelUsageCalculator path and preventing aggregated
provenance from flipping reported to mixed. Reported costs on zero-token
buckets still win via the existing CostUSD!=nil early-continue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tate total The reprice-after-backfill swap accepted any non-nil re-extraction, so a zero-data window (assistant activity before CheckpointTranscriptStart, or a Pi fork abandoning the token-consuming branch) clobbered usage restored from checkpoint state with zeros. The swap now requires real token data. When the session-state total aliases the pre-reprice usage it is repointed to the priced copy, keeping the state diagnostic consistent with the persisted checkpoint.
Released 2026-07-09: sol $5/$30, terra $2.50/$15, luna $1/$6 per MTok, cache reads at 10% of input and — new in this family — cache writes at 1.25x input, all set explicitly. Alias forms cover dated, slash-prefixed, and vertex-style spellings; a guard test pins gpt-5.6-sol-pro (a distinct, differently priced model) to a lookup miss rather than Sol rates. Known limitation documented: >272K-token prompts bill 2x input / 1.5x output, which the flat-rate table cannot express (undercount only).
Keeps CondenseSession under the maintainability threshold and drops the always-empty subagentsDir parameter from tokenUsageWithCost (condensation never passes one; the call-site TODOs document the open question).
…variants
The OpenAI table carried the legacy gpt-5 rate (1.25/10) for gpt-5.5 and
gpt-5.4, which understates their real cost. Correct them to the current
published rates:
- gpt-5.5: 5 / 30 (cache 0.5 / 6.25)
- gpt-5.4: 2.5 / 15 (cache 0.25 / 3.125)
Both carry effective_date 2026-07-10 (the correction date; the old rate was
never actually in effect, it was a copy-paste of gpt-5). gpt-5 itself stays
1.25/10 — that is a genuine legacy rate, not the mistake. Historical persisted
estimates that already billed gpt-5.5/5.4 at the old rate are left as-is: they
are estimate-grade, not invoices, and are not retroactively repriced.
New entries (all effective_date 2026-07-10):
- gpt-5.3-codex: 1.75 / 14 (cache 0.175 / 2.1875).
- gpt-5.5-priority: 12.50 / 75. The cache rates (1.25 / 15.625) are a
2.5x-scaled assumption off the standard gpt-5.5 cache rates — flagged for
verification against the published priority-tier cache pricing.
- claude-opus-4-8-fast: 10 / 50, no explicit cache rates. Omitting them is
deliberate: the Anthropic multipliers derive cache-read 1.0 (0.1x) and
cache-write 12.5 (1.25x) from the fast input base, which is the intended
fast-mode cache economics. Ordered BEFORE claude-opus-4-8 so the dated
"-fast" glob wins over the base "claude-opus-4-8-2*" glob (which would
otherwise capture claude-opus-4-8-<date>-fast).
- cursor.json (new provider file): composer-2 (0.5/2.5), composer-2-fast
(1.5/7.5), composer-2.5-fast (3.0/15.0). Cache rates are Anthropic-ratio
assumptions — verify against cursor.com/docs/models-and-pricing. The
models/*.json embed glob picks the new file up automatically.
The base gpt-5.5 alias is tightened from the broad "gpt-5.5-*" to the
dated-only "gpt-5.5-2*" (matching the gpt-5.6 family convention) so a dated
priority spelling (gpt-5.5-priority-<date>) resolves to gpt-5.5-priority
instead of being glob-captured by gpt-5.5. Both bare ids resolve via the exact
index regardless; the tightening only affects dated/suffixed glob resolution.
Deliberate omissions:
- No opus-4-7-fast: 4-7 is deprecated and its fast-mode rate is inconsistent
with the 4-8 fast base, so it is left out rather than guessed.
- No composer-2.5 standard entry: the non-fast 2.5 rate is unconfirmed.
- No gpt-5.6 priority entries: the priority tier for 5.6 is unpublished.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Claude Code transcripts tag fast-mode turns with usage.speed == "fast" (vs
"standard" or ""). Those turns bill at a premium the standard model rate does
not capture, so per-model attribution must route them to the model's "-fast"
pricing variant (added in the prior commit).
- messageUsage gains a Speed field (json:"speed").
- CalculateModelUsage buckets each kept row under modelKeyWithSpeed(model,
speed): a "fast" turn on claude-opus-4-8 lands in a "claude-opus-4-8-fast"
bucket, everything else stays on the base id. Speed rides the SAME
dedup-kept row as the model (the max-output streaming row), so attribution
stays consistent with the flat token loop and buckets still sum to the flat
total. The flat CalculateTokenUsage path is untouched.
modelKeyWithSpeed is a no-op for standard/empty speed, an empty model, or an id
that already ends in "-fast", so buckets never double-suffix.
Tests:
- claudecode (no pricing import): a new fixture stream_session_fast.jsonl
with a standard + fast turn on claude-opus-4-8 (the fast turn a streaming
duplicate pair proving speed rides the kept max-output row) and a fast turn
on a second model. Asserts the mixed model splits into "claude-opus-4-8"
and "claude-opus-4-8-fast" buckets, the token split, and buckets == flat.
The existing no-speed fixtures/tests prove absent speed keeps the base id
(back-compat). modelKeyWithSpeed has a direct table test.
- agent package (the priced half, respecting the claudecode-must-not-import-
pricing rule): a "claude-opus-4-8-fast" bucket prices at 10/50 ($60 for
1M+1M) while "claude-opus-4-8" prices at 5/25 ($30) via the real embedded
table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Codex can run on OpenAI's priority service tier, which bills at a premium the
standard model rate does not capture. Add an opt-in knob that prices those
turns under the model's "-priority" variant (added earlier).
- PricingSettings gains CodexServiceTier (json:"codex_service_tier"),
accepted by the strict unknown-field-rejecting decoder and applied by
mergePricing. A nil-safe EntireSettings.CodexServiceTier() accessor
defaults to "".
- handleLifecycleTurnEnd computes the pricing model via pricingModelForTier:
for a Codex turn with CodexServiceTier == "priority" (EqualFold) and a
non-empty model, it prices under model+"-priority"; every other agent/tier
is unchanged. The resolved model feeds BOTH the PriceUsage (hook-provided
counts) and CalculateUsageWithCost (transcript) paths. Codex is
transcript-based, so in practice the fallbackModel of CalculateUsageWithCost
is what carries the tier. Settings are loaded only for Codex, so other
agents' turn-end skips the extra read. An empty model or an already
"-priority" id is returned as-is (no double-suffix).
Tests:
- settings: JSON omitempty round-trip, the nil/empty-safe accessor default
"", and a Load() merge of pricing.codex_service_tier through the strict
decoder.
- cli (respecting import boundaries: cli imports agent + pricing): a table
test of pricingModelForTier (codex/priority, case-insensitivity, non-codex
ignored, empty model, already-priority) and an end-to-end priced assertion
that "priority" prices gpt-5.5 under gpt-5.5-priority (12.5/75 -> $87.5 for
1M+1M) while no knob prices under gpt-5.5 (5/30 -> $35) via the real table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Add an opt-in remote pricing layer that reads a locally cached table and merges it beneath user overrides and above the embedded defaults. This is the read/gating half; a later change adds the background refresh that populates the cache. pricing/remote.go: RemoteCache wraps the shared fileSchema (so validateRate and the schema_version==1 contract are reused, not duplicated) with fetch bookkeeping. The cache lives beside the discovery caches under userdirs.Cache(). LoadRemoteEntries is read-only, does no network I/O, and never errors: a missing/corrupt cache, absent Doc, unsupported schema_version, or individually invalid entries all degrade to fewer (or zero) merged entries. settings: pricing.remote opt-in (nil/false default, nil-safe IsRemoteEnabled accessor + ctx-level package func). LoadPricingTable, when enabled, layers remote entries first then user pricing.models so a user override stays authoritative; embedded LoadTable signature is unchanged. Debug-logs the merged entry count, fetched_at, and staleness. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Populate the remote pricing cache with a background daily refresh, so the opt-in read path added earlier has something to merge. pricing/remote.go: RefreshRemoteCache does a versioncheck-style conditional fetch (2s timeout, 1 MiB cap, If-None-Match, entire-cli UA) under a cross-process flock for herd control — a burst of spawned workers collapses to one request via an under-lock FetchedAt re-check. Any failure (network, timeout, non-200, oversized/garbage, or a doc failing the schema/sanity gate) keeps the last good Doc and only bumps FetchedAt, so a broken endpoint is retried at most once per 24h and never corrupts the served table. Writes are atomic (temp + rename). ShouldRefresh gates the 24h backoff; RefreshResult + RefreshRemoteCacheForce drive the manual command. telemetry: generalize the detached spawner into SpawnDetached(exe, args...) (behavior identical: Setpgid/detached, Dir "/", discarded stdio, Start+Release), reused by analytics and the pricing refresh. Triggers (never inline, never blocking): the hidden __refresh_pricing worker runs the fetch; turn-end and root PersistentPostRun spawn it detached only when remote is enabled and the cache is stale. The worker is Hidden, so the post-run parent-chain guard skips it — no fork bomb, no telemetry/version check. spawnDetachedRefresh is a package var so tests prove the triggers only spawn, never fetch inline. Manual surface: `entire tokens pricing-refresh` force-refreshes and reports source, outcome, entry count, fetched_at, staleness, and whether merging is enabled; documented in labs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
The Codex priority-tier knob (pricing.codex_service_tier="priority") was applied at turn-end but discarded at condensation: the committed per-model buckets were re-derived from the raw state.ModelName with no "-priority" suffix, so the persisted checkpoint carried standard rates. A live gpt-5.5 Codex session with the knob set persisted $0.154513 (standard $5/$30) instead of the expected $0.3862825 (priority $12.50/$75, = 2.5x standard). Move the tier-suffix logic into a shared seam both call sites use: settings.PricingModelForAgent (a method plus a ctx-loading package func). It appends "-priority" only for a Codex session on the priority tier, never double-suffixes, and leaves the stored model name raw — the suffix is a pricing-lookup concern applied at the point of pricing. lifecycle.go delegates to it (deleting the duplicate pricingModelForTier); condensation applies it in the tokenUsageWithCost choke point every extraction/repricing path funnels through, so turn-end and condensation now price the same tier.
Real Cursor CLI sessions report the model id "composer-2.5" (the current default), captured live with real token counts, but cursor.json only carried composer-2, composer-2-fast, and composer-2.5-fast, so the id resolved to no rate and cost stayed nil. Add a composer-2.5 entry mirroring composer-2's structure and standard $0.50/$2.50 per-M rate (published rate, same as composer-2; fast tier $3/$15 already present), with cache rates and aliases following the family convention. Exact-match lookup keeps composer-2.5 and composer-2.5-fast distinct with no glob cross-match.
Cursor CLI and Gemini CLI report gemini-3.1-pro and gemini-3.5-flash today, but google.json carried only the 3.0 generation (gemini-3-pro, gemini-3-flash), so those ids resolved to no rate and cost stayed nil. Add both entries mirroring the existing structure, cache convention, and alias style: gemini-3.1-pro at $2/$12 per M (cache read $0.20) and gemini-3.5-flash at $1.50/$9 per M (cache read $0.15). Exact-match lookup keeps the dotted 3.1/3.5 ids from cross-matching the 3.0 "gemini-3-pro-*"/"gemini-3-flash-*" globs, and vice versa.
telemetry.SpawnDetached hardcoded cmd.Dir="/", so the detached
__refresh_pricing worker ran with cwd "/" and its own IsRemoteEnabled check
loaded settings relative to "/". A project-level .entire/settings.json enabling
pricing.remote was therefore invisible and the auto-refresh silently no-oped
forever (manual `entire tokens pricing-refresh` worked because it runs in the
project).
Generalize SpawnDetached to take a leading working-directory argument: analytics
passes "" to keep the platform default ("/" on Unix, temp dir on Windows), while
the pricing refresh passes the process working directory so the worker resolves
project settings via the normal cwd-based loader. A dir that no longer exists
falls back to the platform default (resolveDetachedDir) so a deleted project
directory never fails the spawn. The Windows and no-op build-tagged variants
take the same signature.
A tier-suffixed fallback model (e.g. gpt-5.5-priority from the Codex service-tier knob) only reached the no-calculator fallback bucket. An agent with a ModelUsageCalculator emits buckets under raw transcript ids, which would price at standard rates and silently re-introduce the tier-loss defect on that path. applyTierVariant retargets buckets whose id equals the fallback's base onto the variant id before pricing; other models stay raw since the table has no variant entry for them. No agent on this branch implements a calculator for Codex, so behavior here is unchanged; the seam is exercised by the tier fixture now carrying the turn_context line every real rollout has before its token_counts.
The service-tier knob suffixes whatever model the agent reports, but only gpt-5.5 ships a -priority rate. A gpt-5.3-codex session with the knob set priced under the nonexistent gpt-5.3-codex-priority id: entirely unpriced (nil) instead of the standard-rate estimate, under a bucket id claiming a premium that was never applied. resolveTierFallback keeps a suffixed fallback only when the table prices it, otherwise reverting to the base id — an honest undercount, mirroring the fast-rate rule.
claudecode's modelKeyWithSpeed appends "-fast" to any model reporting usage.speed=="fast", but only claude-opus-4-8-fast has a published fast rate. A fast turn on any other model (e.g. claude-fable-5) buckets as claude-fable-5-fast, which priceBuckets can't resolve -> CostUSD nil: worse than the base-rate estimate, under an id claiming a premium never applied. This is the same failure class resolveTierFallback fixed for the Codex -priority tier, but it bites ModelUsageCalculator buckets, which the fallback-model-only resolveTierFallback never touches. Add a downgradeUnpriceableVariants pass in CalculateUsageWithCost (after the remainder, before pricing) that reverts any bucket whose id carries a known variant suffix (-fast, -priority) to its base id when the table can't price the variant but can price the base; a nil table downgrades too, and a variant whose base is also unpriceable keeps its truthful id. Detection stays truthful at the source; priceability is only known here at pricing time. Rekeying is in place, not merging: same-model buckets are already emitted today (calculator bucket + remainder) and folded by model key downstream and in foldBucketCost, so leaving the duplicate conserves tokens and cost. Priceable variants (claude-opus-4-8-fast) are unchanged.
A shortfall that was only an APICallCount delta (all four token fields fully covered by per-model buckets) still emitted a spurious zero-token remainder bucket. Also defensively normalize a costed-but-unlabeled bucket (non-nil CostUSD with empty CostSource) to CostSourceEstimated in foldBucketCost, so a latent mislabel can never render with no reported/estimated/mixed suffix.
…covery backfillModelAndReprice's identity check (state.TokenUsage == sessionData.TokenUsage) never fires for the Copilot CLI full-session backfill, since sessionStateBackfillTokenUsage assigns state.TokenUsage a distinct object from sessionData.TokenUsage's checkpoint-scoped slice. That left the session-state diagnostic total permanently unpriced once the model was later recovered, even though the persisted checkpoint was priced correctly. Reprice it too, guarded so a zero-data recovery window can't clobber a good value.
doc.go was stale: it said pricing was embedded-plus-settings-overrides only with no runtime fetch. Describe the opt-in remote-pricing refresh (remote.go, gated by pricing.remote) and clarify there is no inline fetch on the hook/condensation path — the refresh is a detached background worker.
modelKeyWithSpeed appended "-fast" to the raw message.model, so a 1m-context fast turn keyed to "claude-opus-4-8[1m]-fast". pricing.Lookup only peels a bracketed "[1m]" suffix when the id ends in "]", so that form resolved to neither the "-fast" variant nor the base rate, leaving the fast premium unpriced. Strip the bracketed long-context marker before appending "-fast" so the key lands on the priceable fast variant (e.g. "claude-opus-4-8-fast"). Co-authored-by: Cursor <cursoragent@cursor.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
https://entire.io/gh/entireio/cli/trails/812
This draft pull request was opened by Entire after CI was requested for the linked trail. Feel free to edit the title or body — the link above is what keeps the trail and PR connected.