Repository navigation
v0.21.3
Fixed
-
A
not_collectedrecord no longer demands the account id the failed
collection could not resolve (#794).mureo_state_platform_not_collected_set
requiredaccount_idwithminLength: 1, while the bulkWritepath the
_mureo-sharedskill documents has always spelled an unknown id"". So
the right answer depended on which write path the agent took — and on the
MCP path, for the one failure whose CAUSE is that no account could be
resolved (Google Ads authenticated with zero accessible accounts, no
customer id configured), there was no legal value to send. The agent
invented one:account_id: "unknown"on both Google Ads and Meta Ads,
which the reports view then read as ONE ad account held under two keys and
withheld the client total over (#793). That tool now accepts""/null
/ whitespace, alone among the platform writers — a platform whose figures
you DID collect knows which account they came from, so
mureo_state_platform_metrics_set,_daily_setand
mureo_state_set_conversion_eventsstill require a real id. An unknown id
is stored as the empty id mureo reads as unknown everywhere (it joins
nothing, including another unknown id, so two platforms that both failed
this way stay two entries), and it is never written over an id the entry
already holds: a platform that failed today still describes the ad
account it described yesterday, and blanking it would break that entry's
per-account conversion override and its place in the duplicate join.
set_platform_not_collectedotherwise behaves exactly as before — the
campaigns, rollups and conversion override are carried over,attempted_at
is server-stamped, andlast_synced_atis not re-stamped.daily-check
step 13 and the_mureo-strategy/_mureo-sharedskills now say what to
pass, and say not to invent a placeholder. -
A placeholder account id no longer joins two platform entries into one
ad account (#793). #794 stops new placeholders from being written; this
covers the documents that already carry one. The account join folded only
a missing, empty or whitespace-onlyaccount_idto unknown, so two
entries an agent had stamped"unknown"— Google Ads and Meta Ads, both
recorded as not collected — were read as one ad account under two keys.
The Reports client list withheld that client's totals, and the repair hint
on screen (mureo repair platform-key --key <key> --drop-duplicate) told
the operator to delete a platform entry that was never a duplicate. These
ids are now read as unknown, compared case-insensitively after trimming
and after theact_prefix is stripped (act_unknownincluded):
unknown,none,null,n/a,undefined,tbd. Nothing else is:
na,-and every other value stay ids, because a plugin platform may
issue a short or punctuated id and folding a real one would silently join
or split genuinely different accounts. There is no migration: a stored
placeholder stays in the file and now reads the same as""— it joins
no other entry (so no duplicate finding, no withheld total, no
--drop-duplicatehint, and--drop-duplicatefinds no duplicate to
remove), it never matches a per-account conversion override, and an
unrecognised key holding it says its ad account is unknown. Writing one
throughmureo_state_platform_not_collected_setstores"", and never
over an id the entry already holds. The four writers that carry collected
figures —mureo_state_platform_metrics_set,
mureo_state_upsert_campaign,mureo_state_platform_daily_setand
mureo_state_set_conversion_events— now refuse a placeholder outright,
naming it and pointing tomureo_state_platform_not_collected_set,
instead of blanking a known entry's id or storing a conversion override
that can never match. One write-guard shift follows from the fold: an
entry holding a placeholder is an unknown entry, so stamping a real id
onto it is now allowed even when another key already holds that id
(before, it was refused as a re-point) — the same repair path an empty
id takes, which makes the duplicate visible to the join and to
mureo repair platform-key.""remains the spelling to write. -
A report card said when it was last written, and an operator read it as
how old the numbers are (#798). The three KPI cells and the "Updated N
hours ago" line under them both come from one rollup,
platforms[<key>].periods[<window>], andfetched_atis stamped at WRITE
time./daily-check's rollup step is best-effort and conditional — it
writes only the windows a run already gathered, must not fire an extra API
call to fill one, and in incremental mode may hold noYESTERDAYwindow at
all — so a run could persist its report and its display, report that it
had, and leave the card untouched. Nothing on the card contradicted it:
the label read "Updated 14 hours ago" over figures covering the day before
yesterday, and aYESTERDAYrollup is not even called stale until two days
after it was written. Nothing stored anywhere said which calendar dates a
rollup covered —periodis the window token andfetched_atis the write
time, and neither answers it. A rollup may now carryperiod_end, the last
calendar date its figures cover (YYYY-MM-DD), besidefetched_at;
staleness is judged on THAT when it is present and parseable, and on the
write time only when it is not. Both are reported either way — they are
different facts, and this is the defect that follows from treating them as
one. The server does not derive it: "yesterday" is yesterday in the ad
account's own timezone, which mureo does not reliably know, and a coverage
date silently off by one is worse than none, so the writer supplies it —
/daily-checkstep 13 and themureo_state_platform_metrics_setschema now
say so. An unparseable value is kept verbatim and decides nothing, exactly
as an unparseablefetched_atalready is. The coverage date is judged
against the UTC calendar date, so an account west of UTC can see its figures
called stale late in its own day, before its grace day has fully run out. -
On screen (#798): where a rollup states its coverage, the client card's
freshness line and the per-platform rows now name the day the figures run to
beside the update time — "To 2026-09-22, updated 14h ago" ("Stale
2026-09-22, updated 14h ago" when mureo will not vouch for them), and
{date}まで・{ago}更新in Japanese. Where no coverage is stated the line
reads exactly as it did. The line is now clipped rather than wrapped: it
carries two facts in a card track ~204px wide, and wrapping it makes every
card in the grid taller; the full line is the element's tooltip. The card's
stale chip, the restated figures under its cells, the alert row above the
grid and the detail view's withheld-figures note now name that same covered
date — "Figures stop at 2026-09-20", "Figures to 2026-09-20, collected
14h ago: …" — whenever the server judged the figures on it, instead of
quoting the write time beside a line that says the opposite. -
A skipped rollup step is no longer silent (#798).
/daily-checkstep 13
must now name which windows it persisted and which it did not, per platform
—windows_not_persisted_for_<platform>, the notation step 4 already uses
foranalytics_not_available_for_<platform>. A run that wrote the report
and the display and said so read as a complete run whether or not the cards
moved, which is what made this easy to miss for days at a time.
Removed
- Unused prompt-building helpers from an earlier LLM design (post-v0.21.2
audit).mureo/google_ads/_intent_classifier.pyand
mureo/google_ads/_rsa_insights.pyare gone, as isMessageMatchEvaluator
(withMessageMatchResult) frommureo/google_ads/_message_match.py, which
now holds onlyLPScreenshotter. The always-empty_suggest_by_intent/
_get_strategic_context_for_intentstubs in
mureo/google_ads/_analysis_search_terms.pyand the branch of
suggest_negative_keywordsthat forwarded to them are removed; its output
is unchanged (the branch never added anything). - Private helpers nothing called (post-v0.21.2 audit):
_resolve_budget_amount_micros(mureo/google_ads/client.py),
get_search_console_client(mureo/mcp/_client_factory.py),
parse_form_body(mureo/web/_helpers.py), andKNOWN_PARTS/
SetupParts.all_installed(mureo/web/setup_state.py). - Deprecated
_load_existing/_atomic_write_jsonaliases in
mureo/providers/config_writer.py(post-v0.21.2 audit). Nothing imported
them since the atomic-JSON helpers moved tomureo/core/atomic_json.py
(#500); importload_existing_json/atomic_write_jsonfrom there.
ConfigWriteErroris still importable fromconfig_writer. google_ads_search_terms_reviewno longer returns the always-empty
intent_analysisfield (post-v0.21.2 audit), whose note claimed an
analysis mureo never runs._apply_intent_analysisand the
use_intent_analysisparameter ofreview_search_terms/
suggest_negative_keywords(mureo/google_ads/_analysis_search_terms.py)
are removed; the MCP tools never exposed that parameter.- Functions and methods that only the test suite called. Nothing in
mureo, its skills or its extensions reached them:
ByodMetaAdsClient.get_metrics_daily/get_ad_set_insights_daily/
get_ad_insights_daily/get_creatives(mureo/byod/_meta_client.py),
byod_active_platforms(mureo/byod/runtime.py),
is_reversible_native_tool(mureo/mcp/native_reversal.py),
ConfigureSession.get_oauth_status_all(mureo/web/session.py),
removable_credential_sections(mureo/web/env_var_writer.py),
_shipped_skill_names(mureo/web/status_collector.py),
ResolvedPeriod.is_preset(mureo/meta_ads/_period.py) and
_reset_update_cache(mureo/web/version_check.py; its tests now reset
the module cache themselves). - Configure-UI strings and styles no screen renders any more (both
locales for the i18n keys):common.back,common.next,common.skip
andauth_wizard.search_console.step1_title, which nothing has rendered
since #99;dashboard.reports_hintand the.dashboard-reports-hint
rule, orphaned by #309;dashboard.reports_client_label, orphaned by
#308; anddashboard.reports_latest_titleand the
.report-latest-periodrule, orphaned by #695. No rendered element used
them, so nothing on screen changes. - The configure UI's cleanup of pre-skills slash commands (post-v0.21.2
audit). Workflow commands became skills in #77 (May 2026); the landing
notice that offered to delete the old~/.claude/commands/*.mdfiles, its
POST /api/legacy/cleanuproute, thelegacy_commands_presentfield of
/api/statusand thelegacy_commandsstep of "Clear all" are removed,
as areHostPaths.commands_dirand thecommands_pathargument of
ConfigureWizard/run_configure_wizard, which only fed that cleanup.
The notice only appeared while those files existed, so a current install
sees no change. If you upgraded from a pre-May-2026 install and still have
them, delete them from~/.claude/commands/by hand:onboard.md,
daily-check.md,rescue.md,search-term-cleanup.md,
creative-refresh.md,budget-rebalance.md,competitive-scan.md,
goal-review.md,weekly-report.md,sync-state.md,learn.md. - The Codex
hooks.jsontop-levelPreToolUsemigration (post-v0.21.2
audit).mureo setup codexand the credential-guard remove path now read
and write only the nestedhooks.PreToolUselist Codex loads; they no
longer move mureo's entries out of the top-level list mureo wrote before
#393. A top-level list left by such a version is not touched (Codex never
loads it, so it is inert) and can be deleted by hand.mureo upgrade
still installs the current guard in the nested list when it finds
mureo's tagged entries only in that top-level list and no nested list
exists yet; once the guard is removed, an upgrade does not bring it back.