| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…1.5.0) `main` did not build and the container image could not be produced: - EF Core 10.0.5 pulled SQLitePCLRaw 2.1.11, which carries a high-severity advisory (GHSA-2m69-gcr7-jv3q). With TreatWarningsAsErrors, NU1903 failed `dotnet build` for four projects. EF Core is now 10.0.12 (SQLitePCLRaw 2.1.12). - The Dockerfile never listed SocialAgent.Providers.Threads.csproj in its restore layer, added in 1.4.0, so `publish --no-restore` failed NETSDK1004. - Nothing in CI built or tested this repo, which is why both reached main. Adds .github/workflows/ci.yml covering build, test and image build. Runtime fixes: - Bluesky cached its access JWT forever and never used the refresh JWT it already parsed, so every call 401'd about two hours after pod start until a restart. Sessions now refresh on 401 and fall back to a full login. - Providers captured a transient typed HttpClient in a singleton, pinning the message handler for the process lifetime and sharing mutable DefaultRequestHeaders across the polling loop and A2A request threads. They now take IHttpClientFactory and set Authorization per request. - /health/ready had no checks registered and reported healthy with the database down. It now runs AddDbContextCheck; liveness stays a process-only check. - Providers fetched one fixed page and filtered `since` client-side, dropping anything beyond it. All three now page to the cutoff. Security: - API keys compare with CryptographicOperations.FixedTimeEquals. - The Threads token moves from the query string to an Authorization header, so it no longer lands in OpenTelemetry spans; the documented query form remains as a fallback. - A missing Authentication:ApiKey outside Development fails at startup. - The pod runs non-root with a read-only root filesystem. EF Core migrations replace EnsureCreated plus hand-written CREATE TABLE patching, in per-dialect assemblies. DatabaseMigrationService adopts a pre-1.5.0 database by recording the baseline as applied; covered by tests on SQLite and opt-in PostgreSQL tests. Not dry-run against live Postgres — back up before the first production rollout. Also batches repository upserts, aggregates analytics in SQL, bounds SkillRouter with a timeout, strips HTML from Mastodon content, and adds host and provider test coverage where there was none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…file Real Docker and PostgreSQL testing surfaced two defects in the previous commit that SQLite-only tests could not see. Migration adoption never ran on PostgreSQL. The legacy-database check gated on IHistoryRepository.ExistsAsync(), which on Npgsql returns true even when no __EFMigrationsHistory table exists. Adoption was therefore skipped and MigrateAsync ran the baseline against an existing schema, failing with 42P07 "relation Notifications already exists" — i.e. a 1.4.0 database would have crash-looped on upgrade. Detection now queries the database catalogue directly for the history and Posts tables, which reads the same on both providers and is also quiet: EF logs its own probe of a missing history table at Error level, which is alarming on a first run. The Dockerfile restore layer did not list the two new migration projects, so `dotnet publish --no-restore` failed with NETSDK1004. The earlier verification simulated the intended project list rather than reading the actual Dockerfile, so it passed while the real `docker build` did not. Verified against PostgreSQL 17 and a built image: - fresh database provisions from migrations; legacy 1.4.0-shaped database is adopted with posts and provider tokens intact; both idempotent across restarts - container runs non-root (uid 1654) with read-only rootfs and all caps dropped - agent card reports 1.5.0; /a2a returns 401 without a key and dispatches skills with one, reading real aggregates out of the adopted database - /health/ready returns 503 while PostgreSQL is stopped and recovers to 200, while /health/live stays 200 throughout Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… optional Inspecting the running cluster explains the deployment's 60 restarts. The previous container exited "Completed" with this in its log: at SocialAgent.Providers.Mastodon.MastodonProvider.GetProfileAsync at SocialAgent.Host.Services.SocialMediaPollingService.ExecuteAsync at Microsoft.Extensions.Hosting.Internal.Host.TryExecuteBackgroundServiceAsync [INF] Application is shutting down... An HttpClient timeout surfaces as TaskCanceledException, which is an OperationCanceledException. Every background service filtered its handler with `when (ex is not OperationCanceledException)` to avoid swallowing shutdown, so a transient network blip talking to Mastodon was deliberately not caught, escaped ExecuteAsync, and the host's default StopHost behaviour terminated the process. Kubernetes then restarted the pod. The handlers now key off the stopping token, which distinguishes real shutdown from an inner operation timing out. The same filter is corrected in the providers, where it defeated the graceful-degradation paths it was written for — a slow endpoint propagated instead of being reported as "not connected" or skipped. Adds regression coverage: SocialMediaPollingServiceTests asserts the service survives a timeout, keeps polling other providers past one failure, and still shuts down cleanly. Verified the timeout test fails against the old filter. Threads ConfigMap and Secret references in deploy/k8s/deployment.yaml are now optional. Threads is disabled and those keys are absent from the deployed ConfigMap and Secret, so applying the manifest as-is would leave the pod in CreateContainerConfigError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Tokens Backing up production showed it runs the 1.3.x schema: Posts, Notifications, Profiles and PollStates, but no ProviderTokens — that table arrived in 1.4.0, which was never deployed. The previous baseline included ProviderTokens, so adopting production would have recorded it as applied while the table did not exist, leaving EF's view of the schema wrong. Migrations now mirror what actually shipped: InitialCreate is the 1.3.x schema and AddProviderTokens is the 1.4.0 addition. Adoption records a migration as applied only when its table is present, and MigrateAsync creates the rest with EF-generated DDL. A 1.3.x database gains ProviderTokens; a 1.4.0 one is left alone. The production schema was diffed against InitialCreate column by column and index by index: identical apart from ProviderTokens. Rehearsed with the 1.5.0 image against a restored copy of the production database (Postgres 16.12): InitialCreate stamped, AddProviderTokens applied, row counts unchanged at 8/12/2/2, zero Error-level log lines, and skills answered from the upgraded data. Adds a 1.3.x-shape test on SQLite and PostgreSQL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Inspecting the production data during the backup showed both providers recording other people's posts as the account's own, with the original author's engagement attached. The live database held two Bluesky reposts credited as own posts with 39,415 and 3 likes, and three Mastodon boosts stored as empty-content posts — every engagement figure the agent reported was inflated by them. Bluesky's getAuthorFeed includes the account's reposts, where the feed item carries the original author and their counts; only items whose author DID matches the session are kept now. Mastodon returns a boost as a status with empty content and the original author's engagement; the statuses request now passes exclude_reblogs=true. This predates 1.5.0 — 1.3.4 has it too. Existing rows are not rewritten by the upsert, so they age out with the 30-day retention window; the CHANGELOG notes the SQL to clear them sooner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hting (1.5.1) Every stored post's LastUpdated equalled the day it was posted: the polling loop asked providers only for posts newer than the last poll, so each post was fetched once, usually minutes after publishing, and its likes, reposts and replies were never updated again. Checked against fosstodon's public API, a production post stored with 0 likes had 1 live. This hit every provider — the Bluesky counts are only right today because 1.5.0 fetched them for the first time. Posts are now re-fetched across a trailing window on every poll and the upsert refreshes their counts. SocialAgent:EngagementRefreshDays (default 7) sets the window; if the last poll is older than the window, after an outage, the fetch reaches back to it so no gap is skipped, and 0 restores the old behaviour. Notifications still fetch only since the last poll. UpsertPostsAsync now returns the number of newly inserted posts, so the "Stored N new posts" log line still means new posts rather than repeating the whole window every five minutes; refreshes are logged at Debug. Version is 1.5.1 because 1.5.0 is already published to Docker Hub from the previous commit, and the tag must keep matching the code it was built from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (1.5.2) The 1.5.0 Bluesky session recovery never ran in production. It refreshed only on HTTP 401, but Bluesky reports a lapsed or rejected access token as 400 with an XRPC error of ExpiredToken or InvalidToken. 1.5.1 therefore failed every Bluesky poll from exactly 2h00m after the pod started — the same outage 1.3.4 had. The unit tests had simulated a 401, so they confirmed the assumption rather than Bluesky's behaviour. The provider now reads the access token's exp claim and refreshes within five minutes of it, so the normal path does not depend on recognising an error response at all. As a fallback it still refreshes and retries once on 401, or on 400 whose XRPC error is ExpiredToken or InvalidToken; other 400s are genuine request errors and still fail. Checked against bsky.social with the production account rather than stubs: access tokens carry exp with a 120-minute lifetime; refreshSession, called exactly as the provider calls it, returns 200 with accessJwt, refreshJwt, did and handle; and the refreshed token is accepted for data calls. Bluesky failures now include the XRPC error name and message. The production log said only "400 (Bad Request)", which is what hid this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
main did not build, the container image could not be produced, and the deployed pod was
restarting on transient network errors. This fixes those, then works through the review findings
behind them.
Verified against a real PostgreSQL 17 server and a built image, not just unit tests — that testing
caught three defects that unit tests alone had passed.
The blockers
The build failed. Microsoft.EntityFrameworkCore.Sqlite 10.0.5 pulls SQLitePCLRaw.lib.e_sqlite3
2.1.11, which carries a high-severity advisory (GHSA-2m69-gcr7-jv3q).
With TreatWarningsAsErrors, NU1903 broke dotnet build for four projects. EF Core is now 10.0.12,
which brings SQLitePCLRaw 2.1.12.
The image could not be built. The Dockerfile never listed SocialAgent.Providers.Threads.csproj
in its restore layer, added in 1.4.0, so restore skipped it and dotnet publish --no-restore failed
with NETSDK1004. This is why the cluster still runs socialagent:1.3.4 while the manifest claimed
1.4.0 — the Threads release was never actually deployable.
Nothing verified this repository. .github/workflows/ held only the squad-* automation, which
is how both of the above reached main. CI now builds, tests, and builds the container image on
every push and pull request.
The restart loop
The deployed pod had 60 restarts, last exiting Completed. Its log:
An HttpClient timeout surfaces as TaskCanceledException, which is an OperationCanceledException.
Every background service filtered its handler with when (ex is not OperationCanceledException) —
meant to avoid swallowing shutdown, but it means a transient blip talking to Mastodon was
deliberately not caught, escaped ExecuteAsync, and the host's default StopHost behaviour killed
the process. The handlers now key off the stopping token, which actually distinguishes shutdown from
an inner operation timing out. The same filter is corrected in the providers, where it defeated the
graceful-degradation paths it was written for.
Covered by regression tests; the timeout test was confirmed to fail against the old filter.
Other runtime fixes
and never used the refresh JWT it had already parsed, so every call returned 401 until a restart.
Sessions now refresh via com.atproto.server.refreshSession on a 401, fall back to a full login,
and retry the failed request.
transient, so resolving it from a singleton factory captured one instance and its message handler
permanently — defeating handler rotation and sharing a mutable DefaultRequestHeaders between the
polling loop and A2A request threads, which HttpHeaders does not support. Providers now take
IHttpClientFactory and set Authorization per request.
registered. Readiness now runs AddDbContextCheck; liveness stays a process-only check so a
database blip does not restart the pod.
client-side. All three now page to the cutoff (Mastodon max_id, Bluesky cursor, Threads after).
Security
response latency no longer leaks a prefix of the key. Repeated X-Api-Key headers are rejected.
url.full on OpenTelemetry spans and out of exception messages. The documented query-parameter
form remains as a fallback if Meta rejects the header.
running and rejecting every request.
RuntimeDefault seccomp.
EF Core migrations
Migrations replace EnsureCreatedAsync plus hand-written CREATE TABLE IF NOT EXISTS patching, in
SocialAgent.Data.Migrations.Sqlite and SocialAgent.Data.Migrations.Npgsql — EF cannot resolve two
providers' migrations from one assembly. DatabaseMigrationService adopts a pre-1.5.0 database
(schema present, no __EFMigrationsHistory) by recording the baseline as already applied.
One trap worth knowing: on Npgsql, IHistoryRepository.ExistsAsync() returns true even when no
history table exists. Gating adoption on it meant adoption never ran on PostgreSQL and MigrateAsync
hit 42P07: relation "Notifications" already exists — a 1.4.0 database would have crash-looped on
upgrade. SQLite does not exhibit this, so SQLite-only tests passed. Detection now queries the
catalogue directly.
Quality
Resilience handlers and 30s timeouts on provider HTTP clients; ValidateOnStart on provider options;
batched repository upserts instead of a SELECT per row; analytics aggregated in SQL rather than
materialising the retention window; a bounded SkillRouter timeout; HTML stripped from Mastodon
content; a cached Mastodon account id.
Testing
78 passing, 0 failing (was: build broken, and 12 tests failing without credentials — integration tests
now self-skip). New coverage for the host, which had none, plus the provider HTTP behaviour.
Verified end to end against PostgreSQL 17 and a built image:
adopted with posts and provider tokens intact; both idempotent across restarts
reading real aggregates out of the adopted database
stays 200 throughout
Deployment notes
real PostgreSQL server, but never against your live data.
keys are absent from the deployed ConfigMap and Secret, so the manifest as it stood would have left
the pod in CreateContainerConfigError.
Not verified
Whether Meta accepts Authorization: Bearer on the Threads refresh endpoint — that needs a live
token, and Threads is not in use. The query-parameter fallback covers a rejection.
🤖 Generated with Claude Code