# Changelog of Reddit Pulse Ai (`netdesignr/reddit-pulse-ai`) Actor

- **URL**: https://apify.com/netdesignr/reddit-pulse-ai/changelog.md
- **Full Actor documentation**: https://apify.com/netdesignr/reddit-pulse-ai.md

## Changelog

All notable changes to Reddit Pulse AI are documented here.

### Unreleased

#### Changed

- Added `residentialProxyFallbackMode` and disabled automatic Apify Residential proxy retries by default to prevent hidden proxy spend on empty runs.
- Removed the default residential proxy configuration from the input schema and documentation.
- Made AI mode BYOK-only by removing the managed Gemini environment-key fallback, preventing hidden Netdesignr third-party AI spend.
- Added a profitability-safe actor-start fee to protect low-result and empty Reddit runs from hidden runtime losses.
- Added a no-premium RSS fallback for subreddit and search listings when Reddit blocks public JSON endpoints from Apify cloud IPs.
- Disabled stale manual `reddit-item` charge calls by default so live billing relies on the configured Apify dataset-item pricing event.
- Reduced the default and maximum memory to 512 MB after live subreddit and search smoke runs proved the browser path remains non-empty at the lower memory tier.

### 0.5.5 - 2026-03-26

#### Changed

- Added a clearer README overview of Reddit content coverage, metadata, and optional AI analysis outputs.

### 0.5.4 - 2026-02-17

#### Security

- **API key redaction in logs** (SEC-RD-003): `geminiApiKey` is now redacted before logging the input object. BYOK users' API keys no longer appear in Apify Console logs.
- **API key leak in Gemini errors** (SEC-RD-004, SEC-RD-005): Error messages from Gemini API are sanitized to strip API key patterns (`AIza...`) before logging or re-throwing. `GeminiClient.toString()` overridden to prevent key leakage via string coercion.
- **Prompt injection: opening XML tag** (SEC-RD-001): `sanitizeContent` now escapes opening `<REDDIT_ITEM` tags (not just closing tags), preventing attackers from injecting fake item boundaries in Reddit content.
- **Prompt injection: brand name quotes** (SEC-RD-002): `sanitizeBrands` now strips double quotes and backslashes from brand names, preventing prompt structure breakage.
- **Proxy SSRF protection** (SEC-RD-006): `parseProxyUrl` now validates proxy hostnames, rejecting private/reserved IPs (localhost, 127.x.x.x, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x, ::1, 0.0.0.0).
- **Protocol-relative URL bypass** (SEC-RD-007): URL classifier now rejects inputs starting with `//` to prevent hostname validation bypass.
- **Input bounds enforcement** (SEC-RD-008): `maxResults` capped at 100,000, `maxCommentsPerPost` at 10,000, `targetBrands` at 50 entries. Exceeding values are clamped with a warning log.

#### Changed

- 673 tests across monorepo (up from 638), 35 new security-focused tests.

### 0.5.3 - 2026-02-17

#### Fixed

- **Default run produced 0 items**: Reddit blocks datacenter IPs. Changed default proxy from empty to Apify residential proxies (`RESIDENTIAL` group). Runs now work out of the box.
- **Silent 0-result "success"**: Actor now exits with error code 1 when 0 items are produced, instead of reporting success.

#### Changed

- Proxy description updated: residential proxies are required, not optional.
- README updated to reflect proxy requirement.

### 0.5.2 - 2026-02-17

#### Fixed

- **includeComments broken for subreddit/search scraping**: Comments were only fetched for direct post URLs. Now `handleSubreddit` and `handleSearch` fetch comments for each collected post after the listing fetch completes. Graceful per-post error handling, one failing post does not affect others.
- **maxResults=0 returned nothing**: The `while (collected < 0)` loop never executed. Now `maxResults=0` is treated as 1000 (all available, up to Reddit's limit).
- **includeUserDetails misleading description**: Clarified that it only applies to user profile URL inputs, not to post authors.

#### Changed

- `fetchPaginatedPosts` now returns `CollectedPost[]` metadata for follow-up comment fetching.
- Extracted `fetchCommentsForPost` as a reusable helper for subreddit/search/post comment fetching.
- Input schema `sort` description now notes that search queries only support hot, new, and top.
- README: added Community output section, `isDeleted`/`isRemoved` to Post/Comment tables, `scrapedAt` to User table, accurate field count (26, or 34 with AI).
- 638 tests across monorepo (up from 620), 278 Reddit tests (up from 260).

### 0.5.0 - 2026-02-17

#### Added

- **Managed AI key**: AI Intelligence Mode now works out of the box. No API key setup needed, just toggle on.
- **Billing module** (`billing.ts`): `getChargeEventName()` and `chargeForItem()` with graceful degradation and full test coverage.

#### Changed

- `resolveApiKey()` now returns `{key, source}` instead of plain string. Source is `'managed'` (env var) or `'byok'` (user input).
- Simplified to 2 billing tiers: base ($2.30/1K) and AI managed (future surcharge). BYOK users pay the same base rate.
- AI Mode section in input schema simplified: "Just toggle on, no setup needed."
- README pricing updated: flat $2.30/1K, AI included at no extra charge.
- 267 tests (up from 258), all passing.

#### Removed

- Em dashes from all codebase content (replaced with commas, colons, or simple dashes).
- Added "No Em Dashes" rule to CLAUDE.md.

### 0.4.3 - 2026-02-17

#### Added

- **`isDeleted` / `isRemoved` fields**: Posts and comments now include boolean flags for deleted (`author === '[deleted]'`) and mod-removed (`body === '[removed]'`) content. Enables downstream filtering without string comparison.
- **`isQuarantined` field**: `CommunityOutput` now includes quarantine status from Reddit's `quarantine` field.

#### Fixed

- **Suspended user handling**: `handleUser` now catches 404 errors from `/user/{name}/about.json` and continues to fetch user activity. Previously a suspended or deleted user profile caused the entire handler to fail.
- **Empty listing warning**: `fetchPaginatedPosts` now logs a warning when zero results are collected, suggesting the subreddit may be private, banned, or misspelled.
- **403 error context**: `handleSubreddit` and `handleCommunityInfo` now wrap 403 errors with actionable context about private or quarantined subreddits instead of surfacing raw HTTP status codes.

#### Changed

- 251 tests (up from 236), all passing

### 0.4.2 - 2026-02-17

#### Fixed

- **Removed `contentType` dead code**: Input parameter "Content Type" (posts/comments/posts\_and\_comments) was visible in Apify Console but had no effect on routing. Removed from input schema, types, config, and validation.
- **Session establishment retry**: Browser session setup now retries once on failure before falling back to cookie-less mode. Previously a single timeout during init would cause all subsequent requests to lack session cookies.
- **onItem emit retry**: Enriched items now get one retry attempt when `pushData` fails transiently. Previously a transient dataset push error would silently lose the scraped + AI-processed item.

#### Changed

- 236 tests (up from 233), all passing

### 0.4.1 - 2026-02-17

#### Security

- **Prompt injection mitigation**: Reddit post/comment bodies are now sanitized before inclusion in AI prompts. Content is wrapped in `<REDDIT_ITEM>` XML delimiters with control character stripping and delimiter escaping. System prompt instructs the model to ignore instructions within user-generated content.
- **Subreddit name validation**: User-provided subreddit names are validated against Reddit's naming rules (alphanumeric + underscore, 2-21 chars). Path traversal, null bytes, and special characters are rejected with warnings.
- **Proxy default fix**: Changed default proxy config from `{useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"]}` to `{}`. Proxy is now opt-in; previous default silently used residential proxies on every run, adding unnecessary cost.

#### Changed

- Empty proxy objects (`{}`) no longer trigger proxy configuration creation
- `sanitizeContent()` exported from ai-prompt.ts for testing
- `sanitizeSubredditName()` exported from main.ts for testing
- README rewritten with improved structure, feature comparison table, limitations section, and cleaner copy
- 233 tests (up from 207), all passing

### 0.4.0 - 2026-02-17

#### Added

- Rate limit handling with automatic retry (429, 503, network errors) via exponential backoff
- Random delay between requests (500-1500ms) to avoid triggering rate limits
- Community info route (`handleCommunityInfo`): fetches `/r/{sub}/about.json` when `includeCommunityInfo` is enabled
- Multi-source deduplication: overlapping inputs (same subreddit in multiple sources) no longer emit duplicate items
- Comment "Load More" expansion: `fetchMoreChildren()` expands collapsed comment threads via Reddit's `/api/morechildren.json`
- AI cost estimation logging: per-batch and summary token/cost estimates for Gemini API usage
- E2E integration tests: 7 tests exercising full pipelines (subreddit, post, search, dedup, AI, community, failure resilience)
- Webhooks & real-time streaming documentation in README
- `shouldRetry` predicate support in shared `retry()` utility
- 207 tests total (up from 176), all passing

#### Changed

- `flattenComments()` now returns both comments and "more" objects (previously discarded "more" objects)
- `ClientLike` interface extended with `fetchMoreChildren` method
- `AI_CONFIG` extended with cost constants (Gemini 2.0 Flash pricing)
- `AiEnricher` tracks batch count, item count, and estimated token usage

### 0.3.3 - 2026-02-16

#### Changed

- Type safety improvements: `ClassifiedUrl` converted to discriminated union
- `TopicLabel` type with `ai_topic.primary` constrained to enum, prevents Gemini from returning invalid topics
- Enum arrays in Gemini response schema derived from TypeScript types (single source of truth)
- Deduplicated `OnItem` callback type across all modules
- Documentation overhaul: comprehensive README matching Apify Store standards
- Added dataset views for Apify Console (posts, AI analysis, comments, detailed)
- Updated input schema with section captions for better UI grouping

### 0.3.2 - 2026-02-16

#### Added

- Runtime validation of AI analysis shape after JSON.parse, catches malformed Gemini responses
- `validateKey` retry logic with descriptive errors for invalid key (401/403), quota (429), and generic failures

#### Changed

- Extracted `parseResponse` method from `analyzeBatch` for clarity

### 0.3.1 - 2026-02-16

#### Added

- Circuit breaker in AI enricher: disables AI after 5 consecutive failures to save API quota
- Per-item try/catch in batch emission loop: one failed `onItem` callback can't lose remaining items
- Brand name sanitization in AI prompts: prevents prompt injection via `targetBrands` input
- `resolveApiKey` function with BYOK + env var fallback and descriptive error messages

#### Changed

- AI setup failure now degrades gracefully, scraper runs without AI instead of crashing
- Flush errors caught and logged instead of crashing actor
- Fatal error handler includes stack trace for debugging

### 0.3.0 - 2026-02-16

#### Added

- AI Intelligence Mode with Google Gemini 2.0 Flash
- 8 analysis dimensions: sentiment, topic, sarcasm, summary, brands, competitors, SEO signals, trend signals
- BYOK (Bring Your Own Key) support for Gemini API
- Batch processing with graceful degradation on AI failure
- Target brand tracking for competitive analysis
- Gemini API key validation at startup
- 138 tests, 98.2% coverage

### 0.2.0 - 2026-02-14

#### Added

- Fast Mode: Core Reddit scraping engine via .json endpoints
- Subreddit posts scraping with pagination
- Post + comments extraction with nested thread flattening
- User profile and activity scraping
- Reddit search with all sort/time filters
- URL auto-classification (subreddit, post, user, search, redd.it)
- Custom User-Agent for Reddit compliance
- Rate limit awareness (429 handling)
- Proxy configuration support (residential recommended)
- Full input schema with 13 configurable options
- 30+ output fields per item
- PPE billing via Apify dataset auto-charge
- 96 unit tests covering all modules

### 0.1.0 - 2026-02-13

#### Added

- Initial actor scaffolding
