Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
- `codeburn sync push --attribution` (opt-in): sends git attribution spans — the session→commit correlation from `codeburn yield` (`codeburn.session.attribution` and `codeburn.commit` span types with normalized repo remote, commit SHAs, merged/reverted state, and PR links). Nothing new is sent without the flag; local-only repos and Windows filesystem paths are never emitted as repo identities, and sessions whose project path no longer resolves never inherit the push-time working directory's repo. See docs/sync/README.md "Git attribution".

### Fixed (CLI)
- **Copilot claude-haiku-4.5 store rows now price correctly instead of $0.** The Copilot session-store.db writes the model as `claude-haiku-4.5` (tier-first, dot), but no pricing alias existed for that raw id, so `calculateCost` returned $0 while GitHub billed the real Anthropic rate (e.g., $0.0063536 for a sampled request). Added an alias to the existing correctly-priced `claude-haiku-4-5` row (no new rate invented). The daily cache bumps from v26 to v27 to force re-derivation of already-finalized days, since this id has no self-heal via prefix fallback. (#1093)
- **A pull request no longer swallows a whole repo's spend.** The working-directory correlation rule attributed every session sharing a checkout with a PR-linked session, with no time bound — a repo whose only captured PR link was pasted once attributed a month of unrelated work (129 of 131 sessions on real data) to that PR. Checkout evidence now only attributes sessions overlapping the linked sessions' own activity window. (#961)
- **Shell reads finally count as reads.** `rg`, `grep`, `cat`, `git log` and friends were invisible to the read-edit-ratio detector (90%+ of real reads uncounted on bash-first workflows) while every Bash call counted as a verification step, so `edit → grep → edit` scored as rework. One shared read-shaped-command classifier fixes both detectors; unknown or mutating commands keep the old behavior. (#941, thanks @laulpogan)
- **Phantom corrections from injected skill prose.** The user-correction detector matched "the wrong answer" inside a templated skill prompt, counting the same non-correction four times on real data; "answer" left the wrong-<noun> pattern list, concrete artifacts (wrong file, wrong approach) still count. (#952)
Expand Down
2 changes: 1 addition & 1 deletion scripts/upgrade-path/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const WORK = process.env['UPGRADE_PATH_WORK'] || join(tmpdir(), 'codeburn upgrad
const OLD_SESSION_CACHE = 'session-cache.v7.json'
const OLD_DAILY_CACHE = 'daily-cache.v17.json'
const NEW_SESSION_CACHE_DIR = 'session-cache.v9'
const NEW_DAILY_CACHE = 'daily-cache.v26.json'
const NEW_DAILY_CACHE = 'daily-cache.v27.json'

const HOME = join(WORK, 'user home')
const PAYLOADS = join(WORK, 'payloads')
Expand Down
9 changes: 7 additions & 2 deletions src/daily-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { join } from 'path'
import { getCodeburnCacheDir } from './cache-dir.js'
import type { DateRange, ProjectSummary } from './types.js'

// Bumped to 27: claude-haiku-4.5 copilot store rows now price correctly (alias added) — #1093.
// Previously the raw id 'claude-haiku-4.5' (tier-first, dot) from session-store.db had no
// pricing alias, so days finalized with this model were stuck at $0. No self-heal via prefix
// fallback exists for this id, so a version bump is required to force re-derivation.
// Bumped to 26: copilot input/cache tokens for sessions covered by the CLI's
// session-store.db move from one shutdown-rollup lump (stamped at session end)
// to per-request DB rows with real timestamps, supplementary accounting calls
Expand Down Expand Up @@ -163,8 +167,9 @@ import type { DateRange, ProjectSummary } from './types.js'
// everyone, which is a lossless no-op for days already correct.
// v25: #1047 activity-id pricing. v24 on main already shipped #1090.
// v26: #946 copilot session-store accounting (see the top of this ladder).
export const DAILY_CACHE_VERSION = 26
const MIN_SUPPORTED_VERSION = 26
// v27: #1093 claude-haiku-4.5 alias (see top).
export const DAILY_CACHE_VERSION = 27
const MIN_SUPPORTED_VERSION = 27

/// Providers whose per-day CALL COUNT means something different at
/// DAILY_CACHE_VERSION 26 than it did before it. Copilot's supplementary
Expand Down
2 changes: 2 additions & 0 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ const BUILTIN_ALIASES: Record<string, string> = {
'anthropic--claude-4.5-opus': 'claude-opus-4-5',
'anthropic--claude-4.5-sonnet': 'claude-sonnet-4-5',
'anthropic--claude-4.5-haiku': 'claude-haiku-4-5',
// #1093: copilot session-store.db writes 'claude-haiku-4.5' (tier-first, dot)
'claude-haiku-4.5': 'claude-haiku-4-5',
'claude-sonnet-4.6': 'claude-sonnet-4-6',
'claude-sonnet-4.5': 'claude-sonnet-4-5',
'claude-opus-4.7': 'claude-opus-4-7',
Expand Down
13 changes: 13 additions & 0 deletions tests/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ describe('getModelCosts', () => {
expect(calculateCost('gpt-5.6-codex-max', 1_000_000, 1_000_000, 0, 0, 0)).toBeGreaterThan(0)
})

it('prices claude-haiku-4.5 (copilot session-store raw id), aliased to the existing claude-haiku-4-5 row (#1093)', () => {
const haiku45 = getModelCosts('claude-haiku-4.5')
const haiku45Dash = getModelCosts('claude-haiku-4-5')
expect(haiku45).not.toBeNull()
expect(haiku45).toEqual(haiku45Dash)
expect(haiku45!.inputCostPerToken).toBe(1e-6)
expect(haiku45!.outputCostPerToken).toBe(5e-6)
expect(haiku45!.cacheWriteCostPerToken).toBe(1.25e-6)
expect(haiku45!.cacheReadCostPerToken).toBe(1e-7)
expect(haiku45!.cacheWriteCostIsExplicit).toBe(true)
expect(calculateCost('claude-haiku-4.5', 1_000_000, 1_000_000, 0, 0, 0)).toBe(6)
})

// A price override on a synthetic bare id can only be reached if the leading
// segment was stripped, so these assert the namespace allowlist itself without
// pinning to any real model's presence in (or absence from) the snapshot.
Expand Down
Loading