Headline numbers
Critical statements by country
Positive statements by country
Timeline
About
PennyWatch is a project of Antipodean Affairs, an independent editorial outfit focused on Australian foreign policy and the public record of those who conduct it.
Our view is that public, on-the-record statements by senior ministers — the things a foreign minister chooses to condemn, what they choose to welcome, who they choose to stand with, who they choose to criticise, and what they choose to stay silent on — are themselves a window into a country's foreign policy posture. PennyWatch records all of them: the critical statements and the positive statements, side by side. Counting them is not a verdict; it is a starting point for an honest argument.
Every entry on this page is sourced to a specific tweet on the Foreign Minister's verified X account, and every rule we use to include, exclude, or tag a statement is published in the methodology below. If you find a misclassified statement, a missing one, or a flaw in the rules, we want to hear about it; the methodology is versioned so corrections are visible.
PennyWatch has no affiliation with any political party, candidate, foreign government, or the Department of Foreign Affairs and Trade.
Methodology
This tracker counts public statements by Senator the Hon. Penny Wong, Australian Minister for Foreign Affairs, directed critically or positively at named foreign countries, governments, or named foreign ministers/officials. Statements are drawn from two sources back to her swearing-in on 1 June 2022: her official X account, @SenatorWong, and her ministerial media releases at foreignminister.gov.au.
Trackers of this kind are only as credible as their inclusion rules. We publish ours in full so readers can audit, dispute, or replicate them.
Sources
Each entry in the dataset carries a source field with one of two values:
- X — a tweet posted by @SenatorWong. Fetched via the X API v2 (timeline + search + conversation chase + direct ID lookup).
- DFAT — a ministerial media release published at foreignminister.gov.au/minister/penny-wong. Scraped politely (~0.5s between requests, descriptive User-Agent identifying the project). Each release is fetched in full and its body text run through the same classifier as tweets.
The two sources are complementary. The X feed catches Wong's day-to-day reactive language, including doorstop-style tweets and thread continuations that don't always appear elsewhere. The ministerial site catches the formal, full-length versions of her substantive statements, including joint statements that are co-signed by other ministers and rarely tweeted in their entirety. Both feed into a single classified table; you can filter to one source at a time using the Source dropdown in the All Statements section.
Joint statements. Many ministerial releases are co-signed with other foreign ministers (G7, Quad, AUKUS, Australia-UK, Australia-Japan, etc.). These are counted, because Wong's name is on them as a signatory, and tagged with a joint statement with note in the table listing the co-signers so readers can apply their own weighting. The methodology does not exclude joint statements; doing so would underrepresent the multilateral dimension of Australian foreign policy, which is most of it.
What counts as a tracked statement
A tweet is included if both of the following are true:
- It contains either a critical or a positive trigger word from the lists below.
- It identifies a target — a country, government, regime, or named foreign minister/official from the target list below.
A single tweet may be both critical and positive (e.g. praising a people while criticising their government). These are recorded as mixed and appear in both views. Pure retweets without added commentary are excluded.
Critical severity tiers
| Tier | Trigger words (non-exhaustive) | Example phrasing |
|---|---|---|
| Condemn | condemn, condemnation, denounce | "Australia condemns…" |
| Criticise | deplore, unacceptable, abhorrent, outrageous, reject, appalled, atrocity, sanctions | "These actions are unacceptable" |
| Concern | concerned, deeply concerned, troubled, alarmed, dismayed, disturbed, regret, (call for/urge) restraint, exercise of restraint, maximum/utmost restraint, "how X defends itself matters", "manner in which X defends" | "deeply concerned by…", "urges restraint", "how Israel defends itself matters" |
| Call for action | must end/stop/cease/release, must be allowed/protected/guaranteed, demand, call(ing/ed) on [X], calling/called for, urge/urging [X] to, immediate/unimpeded ceasefire/access/withdrawal | "calling on Israel to allow aid…", "must release all hostages" |
Positive warmth tiers
Added in v1.2. A critical-only tally tells half a story — a foreign minister's public record is the sum of what they choose to celebrate as well as what they choose to condemn.
| Tier | Trigger words (non-exhaustive) | Example phrasing |
|---|---|---|
| Endorse | congratulate, commend, applaud, salute, warmly welcome, celebrate, honour | "Australia warmly congratulates…" |
| Commend | welcome, proud, pleased to welcome/meet/host, strongly support, delighted | "Pleased to host PM X in Canberra" |
| Appreciate | thank, grateful, appreciate, value, recognise, close partner / partnership / friendship, enduring partnership | "We are grateful for our close partnership with…" |
| Solidarity | thoughts with, hearts go out, condolences, mourn, stand with the people of, in solidarity with | "Our thoughts are with the people of X" |
The Solidarity tier captures expressions of sympathy and standing-with that v1.0 excluded entirely. We now count these as positive-toward statements, because choosing to make them is itself a foreign-policy signal — just as choosing not to make them is.
Target list
A statement only counts if the target is identifiable. We track three target categories.
- Country / government — e.g. "Russia", "Iran", "the Myanmar military regime", "the Israeli cabinet", "Japan", "Indonesia".
- Named foreign official — any named head of state, head of government, cabinet minister, senior military commander, or other senior government official. Criticism (or praise) of a named official is attributed to their country.
- A statement criticising Israeli National Security Minister Itamar Ben-Gvir is recorded as a statement targeting Israel.
- A statement praising Indonesian President Prabowo or thanking PNG PM James Marape is recorded as a positive statement toward Indonesia / Papua New Guinea.
- State entity — e.g. "the IRGC", "the Russian Foreign Ministry", "the IDF General Staff".
Named individuals affiliated with a non-state actor (e.g. Yahya Sinwar / Hamas, Hassan Nasrallah / Hezbollah) attribute to the non-state actor — they do not contribute to any country's total. References to a region without a clear state target ("we are concerned about the situation in the Middle East") are excluded. If one statement targets multiple actors, it is counted once per distinct target.
Sentence-level tone scoping
When a tweet contains both critical and positive language, targets are attributed based on the sentence they appear in. A target only receives a critical tier if it appears in a sentence that contains a critical trigger, and only receives a positive tier if it appears in a sentence with a positive trigger.
Example: "Australia condemns Russia's invasion of Ukraine. We stand with the Ukrainian people." → Russia is recorded as a Condemn target; Ukraine is recorded as a Solidarity target. Russia is not credited with the Solidarity statement, despite both words appearing in the same tweet.
Edge cases
- Joint statements. Tweets attributed to a joint statement (AUKUS, Quad, G7) are included only if Senator Wong personally posts them from her account.
- Domestic political commentary. Excluded.
- Australian citizens detained abroad. Included as critical if they criticise the detaining state; excluded if purely consular/welfare.
- Sanctions announcements. Included as Criticise-tier at minimum.
- Non-state actors (Hamas, Houthis, etc.). Recorded separately. Do not contribute to any country's total unless the tweet separately names a country.
- A tweet praising a people while criticising their government (e.g. "We stand with the brave women of Iran against the regime") is recorded as mixed — both Solidarity-positive and the relevant critical tier, both attributed to Iran.
Known limitations
- Keyword-based classification has false positives. A tweet that says "Australia condemns the attacks by X on Y" tags both X and Y as targets, even though the criticism is only of X. Praise of an individual who shares a name with a country term will mis-attribute. We flag low-confidence cases for review.
- The X feed is not the complete record. Press releases, multilateral joint statements, Senate speeches, and doorstop interviews are not captured.
- The X API is not the complete X feed. The endpoint we use (
/2/users/:id/tweets) does not always return every tweet the account has posted. Replies and conversation continuations are filtered intermittently for reasons X does not document. To compensate, we maintain a smallmanual_tweets.jsonfile of tweets we have noticed the API miss, which is merged into the dataset and classified by the same rules. Tweets added via that mechanism are flagged in the table with a yellow manual badge. Counts of manually-added tweets are reported separately in the build output so the proportion is auditable. - The denominator matters. A raw count of "country X was criticised N times" is not, on its own, evidence of bias. It must be read alongside the newsworthy events involving each country, the criticism levelled by comparable democracies, and the positive-statement count for the same country — which is exactly what the positive view above is for.
- Translation and transliteration. Names with multiple spellings (e.g. "Myanmar" vs "Burma", "Erdoğan" vs "Erdogan", "Ben-Gvir" vs "Ben Gvir") are normalised in the target list.
Changelog
- v1.5.3 — DFAT scraper switched to RSS-only. The foreignminister.gov.au listing+detail pages are WAF-protected against GitHub Actions runner IPs, but the public RSS feed at
/rss.xmlis served unprotected — and its<description>field contains the full release body, not a summary. So the classifier gets the same content as the detail-page scrape would have provided, no proxy needed. Trade-off: the RSS feed only holds the latest 10 items, so DFAT coverage begins from the date of v1.5.3 deployment and grows forward, with no historical backfill. - v1.5.2 — DFAT proxy via a small Cloudflare Worker. foreignminister.gov.au's WAF blocks GitHub Actions runner IPs (silent TCP-accept-then-drop pattern), so the scraper now routes requests through an authenticated Worker on Antipodean Affairs's Cloudflare account. The Worker is allowlisted to only Wong's ministerial subtree and caches responses at the edge for 5 minutes so repeated requests don't burden the upstream. fetch_dfat.py falls back to direct fetch when the proxy env vars are absent (local development).
- v1.5.1 — Two fixes after v1.5.0's first run returned zero DFAT records. The polite "Mozilla/5.0 (compatible; PennyWatch; +URL)" User-Agent was being silently blocked by foreignminister.gov.au's WAF — switched to a Firefox-shaped UA with a PennyWatch identifier appended. Also fixed a first-run bug where the incremental clock for DFAT was using the latest X-tweet date as the reference, so the very first DFAT run only fetched the last week instead of doing the full June-2022 backfill.
- v1.5.0 — Dual-source dataset. PennyWatch now combines tweets from @SenatorWong with ministerial media releases scraped from foreignminister.gov.au. Every record carries a
sourcefield (xordfat); joint statements are tagged with ajoint_withlist naming co-signers. The classifier is the same; the inclusion rules are the same. The table has a new Source filter, and each row carries an X or DFAT badge. - v1.4.2 — Two exclusion fixes. Perpetrator-pattern (mirror of v1.3.2 victim-pattern): countries appearing in "X's invasion", "X's oppressive regime", "against X", or "hold X accountable" constructions are excluded from positive sentiment and tagged Condemn. Fixes the "Russia tagged positive in 'stands with Ukraine against Russia's invasion'" misclassification. Location-context: countries appearing in "depart X", "leaving X", "evacuate from X", "stranded in X" constructions are excluded from positive sentiment — they're the location of trouble, not the recipient. Fixes the "Israel/Palestine tagged positive in tweets thanking Jordan/UAE for helping Australians leave the conflict zone" pattern. Attribution logic also refactored: severity from explicit critical triggers propagates to non-excluded targets, while severity forced by perpetrator-context only attaches to the perpetrator country itself.
- v1.4.0 — Incremental fetching. Each daily refresh now fetches only tweets posted since the most recent tweet in the existing dataset (minus a 5-day overlap window to catch boundary misses), rather than re-pulling the full archive every run. Cuts typical daily X API cost from ~2,000 reads to ~5–50. The classifier still runs over the entire merged dataset on every refresh, so trigger updates apply retroactively. A
--full-fetchflag is available for rare cases when a full re-pull is needed. - v1.3.8 — Diplomatic-protest action triggers. Calling in or summoning a foreign ambassador now counts as a Concern; expelling theirs or recalling ours counts as Criticise. Surfaced by a thread continuation tweet ("directed DFAT to call in Israel's Ambassador…") that the v1.3.7 fetch was capturing but the classifier was silently dropping due to missing trigger vocabulary.
- v1.3.7 — Direct ID lookup via
/2/tweets?ids=…. Tweet IDs listed inmissed_ids.jsonare batch-fetched directly, bypassing the heuristic filtering applied to timeline and search queries. Tweets surfaced this way carry a direct-id badge in the table. Used in cases where the broader queries (timeline, search, conversation-chase) all silently filter a tweet that does exist. - v1.3.6 — Conversation chasing. For every conversation Wong started in the last 7 days, the scraper now issues an explicit
from:SenatorWong (conversation_id:A OR conversation_id:B…)query. This bypasses X's heuristic filtering of self-replies and surfaces thread continuations the broader queries miss. - v1.3.5 — Thread-continuation coverage. v1.3.4 captured thread leads but X was still filtering Wong's self-replies (her follow-up tweets within the same thread). The search step now runs two queries —
from:SenatorWongandfrom:SenatorWong is:reply— and merges results, which restores those thread continuations to the dataset. - v1.3.4 — Reworked the scraper to surface replies and thread continuations that the X API was over-filtering. Dropped the server-side
exclude=retweetsparameter (it was dropping replies as collateral) and now filter retweets client-side by inspectingreferenced_tweets[].type. Added a parallel fetch from the/2/tweets/search/recentendpoint as a second source — different filtering, better combined coverage. Addedcall for/calls forclassifier patterns to catch "our call for the release of detained Australians" phrasing that the earlier gerund-only patterns missed. - v1.3.3 — Acknowledged a known limitation of the X API: the user-timeline endpoint does not always return every tweet, particularly replies. Added a
manual_tweets.jsonmechanism so any tweet that the auto-fetch misses can be added by hand and runs through the same classifier. Manually-added tweets are flagged with a badge in the table. Expanded the X API request fields and added expansions to maximise reply coverage from the API. - v1.3.2 — Added victim-pattern exclusion. When a sentence describes violence done TO a country ("attacks on Israel", "killing of Israeli embassy staff", "bombing of Russian diplomats"), the country named is the victim, not the target of the criticism. The classifier now strips victim countries from the critical-target list for that sentence. Same shape as the co-signatory and descriptive-construction exclusions. Fixes the long-standing "condemns attacks on X by Y" false positive that was wrongly tagging X as a criticism target. Content-neutral, applies symmetrically to any country.
- v1.3.1 — Closed Middle East country coverage gap (added Lebanon, Jordan, Egypt, UAE, Qatar, Iraq, Yemen, Libya; added "Hizballah" spelling). Added diplomatic-restraint patterns to the Concern tier (content-neutral, fires identically across all targets). Added descriptive-construction exclusion: country names in attributive phrases like "Indonesian-funded hospital" or "Iranian-backed militia" describe a noun, not the statement's target, so they're stripped from per-sentence target lists. Honest consequence: some tweets that were mis-tagged in v1.3 are now correctly classified as having no identifiable country target and dropped from the dataset entirely.
- v1.3 — Broadened the Call-for-action trigger list to capture grammatical forms (gerund/past tense/multi-word "urge X Y to") and passive constructions ("must be allowed/protected") missed in v1.2. Surfaced by a noted false negative on a 2025-04-24 Israel/Gaza statement where the call on Israel was missed and only the call on Hamas was recorded. Added co-signatory exclusion: countries appearing in "Australia joins X in calling on Y" constructions are recognised as joining the statement, not as its target. Content-neutral: the same patterns apply to every target equally.
- v1.2 — Added positive-statement tracking. Clarified that criticism or praise of any named cabinet minister or senior official attributes to their country. Substantially expanded the country list. Introduced sentence-level tone scoping.
- v1.0 — Critical-only tracker with four severity tiers.
All classified statements
| Date | Tone | Tier | Targets | Statement |
|---|