Resource History Guide
Overview
Section titled “Overview”FinFocus maintains a Resource History Store that tracks which cloud resource IDs existed over time. This enables accurate actual cost queries across full billing periods, even when resources are replaced or destroyed mid-month.
Why this exists: Cloud provider billing APIs report costs by resource ID
(e.g., EC2 instance i-abc123). When Pulumi replaces a resource, the old cloud
ID disappears from state and a new one takes its place. Without history, FinFocus
can only query costs for the current resource — missing the old one entirely.
Target Audience: End Users, DevOps Engineers, Platform Engineers
Prerequisites:
- FinFocus CLI installed
- Pulumi project configured with at least one plugin
Estimated Time: 10 minutes
Table of Contents
Section titled “Table of Contents”- How It Works
- Configuration
- Data Sources
- Cost Allocation Tags
- Storage and Retention
- Backup and Recovery
- Troubleshooting
How It Works
Section titled “How It Works”Every time FinFocus runs (finfocus overview, finfocus cost actual, or during
pulumi up with the analyzer), it records a snapshot of your current resources:
March 1 run: web-server → i-old123 (recorded)March 15: Resource replaced (Pulumi up)March 16 run: web-server → i-new456 (recorded)March 30: finfocus cost actual --from 03-01 --to 03-31 → Queries costs for BOTH i-old123 AND i-new456 → Returns full month: $100 (not just $50)The history store tracks three pieces of information per observation:
| Field | Description |
|---|---|
| URN | Pulumi logical resource name (stable across replacements) |
| Cloud ID | Physical cloud resource ID (changes on replacement) |
| Timestamps | First seen / last seen dates for this (URN, Cloud ID) pair |
When the same URN gets a new cloud ID (replacement), both entries are preserved. When you query actual costs, FinFocus looks up all cloud IDs that existed during the requested period.
Query Strategy: ID-First, Tag-Fallback
Section titled “Query Strategy: ID-First, Tag-Fallback”FinFocus uses a layered query strategy:
- Primary: Query billing APIs by all known cloud IDs from the history store
- Fallback: For resources with no history (cold start, created and destroyed between runs), query billing APIs by configured cost allocation tags
- Deduplication: Costs from tag-based queries are deduplicated against ID-based results to prevent double-counting
Terraform Projected Cost History
Section titled “Terraform Projected Cost History”Terraform projections record cost snapshots in the existing cost history database.
This timeline is separate from the Pulumi resource identity observations described
above. cost history view, list, diff, export, and prune open the Terraform
timeline without running a plan.
By default, the identity combines the normalized Git origin with the Terraform
root’s path relative to the Git root, and uses the stack name unscoped. Without
Git or origin, it uses the cleaned absolute path. The database filename contains
a hash; the stored display label contains the path, without the raw remote URL.
To keep separate histories for Terraform workspaces:
finfocus cost projected --include-workspacefinfocus cost history list --include-workspaceUse the same setting when recording and reading snapshots. The flag overrides
cost.terraform.include_workspace for one run; --include-workspace=false
overrides a configured true. The workspace command runs only when enabled.
The workspace name unscoped is reserved. Pulumi history identities ignore this
flag.
cost history collect reads Pulumi checkpoints. In a Terraform directory it exits
2 without running a plan because Terraform plans contain no checkpoint list.
For actual costs, pass --terraform-state; FinFocus does not plan for actual costs.
Configuration
Section titled “Configuration”cost: history: enabled: true # default: true retention_days: 90 # how long to keep entries (default: 90) allocation: enabled: false # default: false (opt-in) tags: # tags for billing API fallback queries - "pulumi:project" - "pulumi:stack"| Key | Type | Default | Description |
|---|---|---|---|
cost.history.enabled |
bool | true |
Enable resource history tracking |
cost.history.retention_days |
int | 90 |
Days to keep history entries |
cost.allocation.enabled |
bool | false |
Enable tag-based billing queries |
cost.allocation.tags |
list | [] |
Tag keys to query billing APIs by |
Data Sources
Section titled “Data Sources”The history store is fed by multiple sources automatically:
State Snapshots (Automatic)
Section titled “State Snapshots (Automatic)”Every finfocus overview or finfocus cost actual run reads the current Pulumi
state and records all resources. This is the primary data source and requires no
configuration.
What it captures: URN, cloud ID, resource type, provider, resource tags.
Pulumi Cloud Update History (Opt-In)
Section titled “Pulumi Cloud Update History (Opt-In)”Pulumi Cloud history fills observation gaps by reading state snapshots from past
update, refresh, destroy, and import operations. Imported observations
have source pulumi_cloud. FinFocus extracts only URN, cloud ID, resource type,
and provider; it caches those identities in history.db. It never stores
snapshot properties or secrets.
config set writes the global config.hujson (~/.finfocus/ by default), so
the setting applies to every project. To enable it for one project only, add
it to that project’s .finfocus/config.hujson instead. Run the import and
cost commands from the project directory:
finfocus config set cost.history.pulumi_cloud.enabled autofinfocus --import-cloud-history --from 2026-10-01 --to 2026-10-27finfocus cost actual --from 2026-10-01 --to 2026-10-27 --verboseThe dashboard Config page exposes the same setting as an enum with false,
auto, and true choices. The default is false, which makes no requests.
auto uses local history when Cloud history is unavailable and warns when an
import is incomplete. true fails before reporting totals when Cloud history
cannot be read completely. Both modes require cost.history.enabled: true.
cost actual imports before it reads local resource history. --verbose sends
import counters to stderr. A separate --import-cloud-history run prepares
history without calculating costs or starting a plugin. Overview and the web
Cost page read stored history and never fetch Cloud snapshots themselves.
Cached Cloud snapshots expire with the rest of the history under
cost.history.retention_days. If Pulumi Cloud asks for a wait longer than 30
seconds (Retry-After), FinFocus stops retrying and treats the import as
unavailable instead of waiting.
FinFocus reuses the Pulumi CLI login. There is no config token key. To override
the saved token, use PULUMI_ACCESS_TOKEN. For a backend selected in a project’s
backend.url, an override also requires explicitly setting PULUMI_BACKEND_URL;
otherwise FinFocus uses only credentials saved for that project backend URL.
The import range is clamped to the retention cutoff. Retention resolves from
FINFOCUS_HISTORY_RETENTION_DAYS, then cost.history.retention_days, then 90
days, matching history cleanup. Without --from, the import command starts at
that cutoff; --to defaults to now. Repeated runs reuse cached identities.
Known limit: Only snapshots observed within the requested period add identities. A resource created before the period and destroyed inside it may have no in-period snapshot containing its ID, so Cloud history does not add it. Previously stored observations and billing tags can still provide coverage.
Plan Lineage (From Preview)
Section titled “Plan Lineage (From Preview)”When finfocus overview runs pulumi preview, it extracts old and new cloud IDs
from replacement and deletion operations. This captures the outgoing resource
ID that would otherwise be lost.
What it captures: Old cloud ID from OldState on replace/delete steps.
Analyzer Events (From Pulumi Up)
Section titled “Analyzer Events (From Pulumi Up)”When FinFocus is configured as a Pulumi analyzer, it records resource observations
during every pulumi up operation in real-time.
What it captures: URN, type, provider, properties per analyzed resource.
Cost Allocation Tags
Section titled “Cost Allocation Tags”For the most complete cost coverage — especially for resources that are created and destroyed between FinFocus runs — configure cost allocation tags on your cloud resources.
Key requirement: Tags must be present on the resource while it incurs costs. Cloud billing systems snapshot the tag state per cost record. You cannot retroactively tag resources and have historical costs updated.
AWS Setup
Section titled “AWS Setup”AWS supports provider-level default tags that are applied to all resources:
config: aws:defaultTags: tags: pulumi:project: my-app pulumi:stack: devOr in code for dynamic values:
provider, _ := aws.NewProvider(ctx, "tagged", &aws.ProviderArgs{ DefaultTags: &aws.ProviderDefaultTagsArgs{ Tags: pulumi.StringMap{ "pulumi:project": pulumi.String(ctx.Project()), "pulumi:stack": pulumi.String(ctx.Stack()), }, },})After tagging: Activate pulumi:project and pulumi:stack as
Cost Allocation Tags
in the AWS Billing Console. Tags take approximately 24 hours to appear in
Cost Explorer after activation. Historical costs from before activation are
not retroactively tagged.
GCP Setup
Section titled “GCP Setup”GCP labels must be lowercase with no colons. Use underscores instead:
config: gcp:defaultLabels: pulumi_project: my-app pulumi_stack: devFinFocus automatically normalizes pulumi:project to pulumi_project when
querying GCP billing exports.
GCP v8.0+ automatically adds the
goog-pulumi-provisionedlabel. To opt out, setgcp:addPulumiAttributionLabel = false.
Azure Setup
Section titled “Azure Setup”Azure Native does not have a provider-level default tags option. Use a stack transformation:
ctx.RegisterStackTransformation(func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult { if tags, ok := args.Props["tags"]; ok { if tagMap, ok := tags.(pulumi.StringMap); ok { tagMap["pulumi:project"] = pulumi.String(ctx.Project()) tagMap["pulumi:stack"] = pulumi.String(ctx.Stack()) args.Props["tags"] = tagMap } } return &pulumi.ResourceTransformationResult{Props: args.Props, Opts: args.Opts}})FinFocus Tag Configuration
Section titled “FinFocus Tag Configuration”After setting up tags on your cloud resources, configure FinFocus to use them:
cost: allocation: enabled: true tags: - "pulumi:project" - "pulumi:stack" - "Environment" # well-known billing tag - "CostCenter" # well-known billing tagWell-Known Cost Allocation Tags
Section titled “Well-Known Cost Allocation Tags”These tags are commonly used for cost attribution across cloud providers:
| Tag Key | Description | AWS | GCP | Azure |
|---|---|---|---|---|
pulumi:project |
Pulumi project name | pulumi:project |
pulumi_project |
pulumi:project |
pulumi:stack |
Pulumi stack name | pulumi:stack |
pulumi_stack |
pulumi:stack |
Environment |
dev/staging/prod | Environment |
environment |
Environment |
CostCenter |
Finance cost center | CostCenter |
cost_center |
CostCenter |
Team |
Owning team | Team |
team |
Team |
Application |
Application name | Application |
application |
Application |
Storage and Retention
Section titled “Storage and Retention”File Location
Section titled “File Location”~/.finfocus/history/history.db # Resource history (BoltDB)~/.finfocus/cache/cache.db # Cost result cache (BoltDB, separate)History vs Cache
Section titled “History vs Cache”| Property | History (history.db) |
Cache (cache.db) |
|---|---|---|
| Purpose | Resource identity timeline | Cost query results |
| Lifecycle | Persistent, accumulates | Ephemeral, rebuildable |
| Safe to delete? | No — loses identity data | Yes — rebuilds on next query |
| Retention | Configurable (default: 90 days) | TTL-based (default: 1 hour) |
| Grows over time? | Yes, bounded by retention | No, bounded by TTL + compaction |
Retention Cleanup
Section titled “Retention Cleanup”On startup, FinFocus removes history entries with last_seen older than the
retention window. Default retention is 90 days.
For long-running environments, consider increasing retention to cover full billing cycles:
cost: history: retention_days: 365 # keep 1 year of historyBackup and Recovery
Section titled “Backup and Recovery”Why Back Up History
Section titled “Why Back Up History”The history database contains the resource identity timeline that enables accurate month-long cost queries. If lost:
- Cost queries for past periods may undercount (missing replaced/destroyed resources)
- The timeline rebuilds gradually as FinFocus runs, but past observations are lost
Backup
Section titled “Backup”# Simple file copy (stop finfocus first for consistency)cp ~/.finfocus/history/history.db ~/.finfocus/history/history.db.bak
# Or include in your infrastructure backuptar -czf finfocus-history.tar.gz ~/.finfocus/history/Recovery
Section titled “Recovery”# Restore from backupcp ~/.finfocus/history/history.db.bak ~/.finfocus/history/history.dbIf the history database is corrupted, FinFocus will automatically detect the corruption and recreate the database (following the same pattern as the cache store). Corrupted data is lost, but FinFocus continues to function with reduced historical accuracy.
Troubleshooting
Section titled “Troubleshooting”History not recording
Section titled “History not recording”- Check that history is enabled:
finfocus config get cost.history.enabled - Verify the history directory is writable:
ls -la ~/.finfocus/history/ - Enable debug logging:
finfocus --debug cost actual ...and look for history-related log entries
Costs seem incomplete for past months
Section titled “Costs seem incomplete for past months”This usually means FinFocus didn’t run while a resource existed:
- Check history coverage: resources must have been observed by at least one FinFocus run during their lifetime
- Consider enabling tag-based fallback (
cost.allocation.enabled: true) for gap coverage - For CI/CD environments, schedule regular FinFocus runs (daily or on every
pulumi up) to minimize observation gaps
History database growing large
Section titled “History database growing large”The database is bounded by retention_days. If it’s larger than expected:
- Reduce retention:
cost.history.retention_days: 30 - Restart FinFocus to trigger cleanup
- For stacks with many resources, a larger history file is normal
Tag-based queries returning unexpected results
Section titled “Tag-based queries returning unexpected results”- Verify tags are on your cloud resources: check the AWS/GCP/Azure console
- For AWS: confirm tags are activated as Cost Allocation Tags in Billing Console
- Check tag key format: GCP uses underscores (
pulumi_project), not colons - Tags must have been present while costs were incurred — retroactive tagging does not apply to historical billing data