* Fixed world purges leaving orphaned entity data
co_entity has no world column, so /co purge r:#world deleted the kill
rows in co_block but kept every co_entity row. Those blobs became
orphans that no later purge removed.
- SQLite: copy only the co_entity rows that a retained kill row still
references. This also drops orphans left by earlier purges.
- MySQL/DuckDB: delete the co_entity rows of the kill rows a world purge
removes, before co_block is purged. MySQL uses a join so MariaDB and
MySQL 5.7 do not run a dependent subquery. Global purges keep the
cheaper time-based delete, which is equivalent. If MySQL stops between
the two deletes, running the same purge again removes the remaining
kill rows.
Player kills use the kill action with type 0 and a user id in data, so
they are never treated as co_entity references.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Fixed SQLite purges with world and block filters purging other tables
On SQLite, /co purge r:#world i:<block> built the retain condition for
world-scoped tables without checking the block restriction, so it purged
co_container, co_chat and the other world-scoped tables in that world
although a block restriction should leave them untouched. The
entity_container/entity_interaction branch and the MySQL path already
check it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Fixed console purges with a radius purging all worlds
/co purge rejects a numeric radius, but the check only saw the radius
when the sender had a location. From the console, /co purge t:30d r:50
parsed no radius and no world, so it ran as a server-wide purge. The
radius is now also parsed against a placeholder location, so it is
rejected for every sender. r:#world is unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Added orphaned entity data cleanup to MySQL #optimize purges
MySQL deletes in place, so co_entity rows orphaned by earlier world
purges stay until something removes them. With #optimize, delete every
co_entity row that no kill row references, through a temporary table of
referenced ids (avoids an anti-join on the unindexed co_block.data), then
let OPTIMIZE reclaim the space. Failures are reported like the table
loop, so the entity_spawn link cleanup still runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Moved purge conditions into PurgeFilter
PurgeFilter now builds the purge condition for every table, and the
SQLite copy, the SQLite recovery path and the MySQL/DuckDB delete all use
it instead of three hand-built copies. Behavior is unchanged; the block
restriction check that the previous commit added to the SQLite copy is
part of the shared condition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Added entity kill filters to purge command
/co purge rejected entity types and actions, so mob farm kill logs could
only be removed together with all other data. New arguments on SQLite,
MySQL and DuckDB:
- a:kill purges only entity kill rows.
- i:<entity> purges kills of those entity types; it can be combined with
block types.
- e:<entity> keeps kills of those entity types while purging the rest.
Each purged kill also removes its co_entity row. a:kill with block types,
entity types in both i: and e:, e: with only block types in i:,
non-entity exclusions and entity filters on ClickHouse are rejected. Any
a: value other than a kill alias is rejected instead of falling through
to an unrestricted purge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Added radius support to purge command
/co purge rejected any numeric radius, so kills of one entity type could
only be purged across a whole world. A radius is now accepted when the
purge is restricted to co_block rows with i: or a:kill, for example
/co purge t:30d r:50 i:zombie. r:50x10 also limits the height.
The radius becomes an x/y/z range in the co_block condition. co_entity
rows follow the purged kill rows, so no entity data is orphaned, and the
(wid,x,z,time) index already covers the range. Other tables are left
untouched, as with any restricted purge.
Rejected: a radius without i: or a:kill, WorldEdit selections, radius
purges on ClickHouse, and a numeric radius from the console (it has no
location, as in the console radius fix merged into this branch).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdEuKbjcVoVinVQJq4te1f
* Made MySQL entity data purges safe against concurrent writers
The #optimize orphan sweep snapshots the co_entity ids that kill rows
reference, then deletes every row missing from the snapshot. Another
installation sharing the database could commit a kill between the two
statements and lose its entity data. The sweep now only deletes rows
inside the purge time range. A kill always writes its co_entity row with
the current time, and a purge range ends at least 24 hours ago, so a
concurrent kill never matches.
A world purge deletes the entity data of the purged kills and
then the kill rows. On MySQL a failure or cancellation between the two
left kept kill rows without their rollback data. Both deletes now run in
one transaction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Limited SQLite entity data cleanup to the purge time range
A SQLite purge dropped every co_entity row no kill referenced, including
rows newer than the purge range, and counted them as deleted. It now only
removes unreferenced rows inside the range, like the MySQL #optimize sweep.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rejected purge include and exclude lists without a value
/co purge t:30d e: passed validation, parsed to no exclusions, and ran
as an unrestricted time purge. e:, and e:,,, did the same, as did an
e: at the end of the command and an empty i:. The argument check now
follows each include or exclude list across its continuation tokens and
rejects a list that ends without a value. Space-separated lists such as
e:zombie, villager still work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Matched purge entity filters to kills logged under renamed types
Bukkit resolves a legacy name such as zombie_pigman to the renamed type,
so the filter only matched the id of the new name. e:zombie_pigman then
purged the old kills it was meant to keep, and i:zombie_pigman missed
them. The filter now covers every entity map id that resolves to the type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rejected purges with more than one radius argument
/co purge t:30d r:50 r:#world_nether i:zombie ignored the explicit world
and purged around the player in their current world. Two r: arguments
always conflict, so the second one is now rejected before the purge
starts, whether it is a world, #global or another radius.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Applied max-radius to radius purges
A radius purge accepted any value. A radius past the int range saturated,
the area overflowed, and the purge reported success with nothing deleted.
It now stops at max-radius like lookups and rollbacks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Checked radius arguments inside purge include and exclude lists
/co purge t:30d i:zombie, r:50 r:#world_nether passed the second-radius
check: the validator took r:50 as a value of the include list, then saw
r:#world_nether as the only radius. The radius and world parsers read
both. A list now continues only with plain or minecraft: namespaced
values, so a radius after a trailing comma is counted and the second one
is rejected. This is the same change as 39deec3 on entity-kill-purge-filters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFQuzYmYun57wJi2a5mqkM
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: TonyJamesStark <tonyjamesstark1@gmail.com>
14 KiB
Commands
You can access the following commands by using /co.
Command Overview
| Command | Description |
|---|---|
| /co help | Display a list of commands |
| /co inspect | Toggle the inspector |
| /co lookup | Lookup block data |
| /co rollback | Rollback block data |
| /co restore | Restore block data |
| /co purge | Delete old block data |
| /co reload | Reload the configuration file |
| /co status | View the plugin status |
| /co consumer | Toggle consumer processing |
| /co migrate-db | Migrate between database backends |
Alias Commands
| Command | Description |
|---|---|
| /co near | Performs a lookup with a radius of 5 |
| /co undo | Revert a rollback/restore via the opposite action |
Command Details
Detailed command information is listed below.
/co help
Display a list of commands in-game.
/co inspect
Enable the inspector. Type the command again to disable it. You can also use just "/co i".
/co lookup
Perform a lookup. Nearly all of the parameters are optional.
| Command | Parameters |
|---|---|
| /co lookup | u:<user> t:<time> r:<radius> a:<action> i:<include> e:<exclude> |
| /co l | /co lookup <params> |
Parameters
| Parameter | Description |
|---|---|
u:<user> |
Specify the user(s) to lookup. |
t:<time> |
Specify the amount of time to lookup. |
r:<radius> |
Specify a radius area to limit the lookup to. |
a:<action> |
Restrict the lookup to a certain action. |
i:<include> |
Include specific blocks/entities in the lookup. |
e:<exclude> |
Exclude blocks/entities from the lookup. |
f:<filter> |
Include or exclude chat, command, or sign text by prefix. |
#<hashtag> |
Add a hashtag to perform additional actions. |
Pagination
If multiple pages are returned, use the command /co lookup <page> to switch pages.
To change the number of lines displayed on a page, use /co lookup <page>:<lines>.
For example,
/co l 1:10will return 10 lines of data, starting at the first page.
/co rollback
Perform a rollback. Uses the same parameters as /co lookup.
Rollbacks can be used to revert player actions.
| Command | Parameters |
|---|---|
| /co rollback | u:<user> t:<time> r:<radius> a:<action> i:<include> e:<exclude> |
| /co rb | /co rollback <params> |
/co restore
Perform a restore. Uses the same parameters as /co lookup.
Restoring can be used to undo rollbacks or to restore player actions.
| Command | Parameters |
|---|---|
| /co restore | u:<user> t:<time> r:<radius> a:<action> i:<include> e:<exclude> |
| /co rs | /co restore <params> |
/co purge
Purge old block data. Useful for freeing up space on your HDD if you don't need the older data.
| Command | Parameters |
|---|---|
| /co purge | t:<time> r:<world/radius> i:<include> e:<exclude> a:kill |
For example, /co purge t:30d will delete all data older than one month, and only keep the last 30 days of data.
If used in-game, only data older than 30 days can be purged.
If used from the console, only data older than 24 hours can be purged.
Purging Worlds
You can optionally specify a world in CoreProtect v19+.
For example, /co purge t:30d r:#world_nether will delete all data older than one month in the Nether, without removing data in any other worlds.
Purging by Radius
A radius can limit a purge of specific block types or entity kills to the area around you. It only applies to block and entity kill data, so it must be combined with i: or a:kill, and it can only be used in-game. A radius always applies to the world you are in, so it cannot be combined with a world such as r:#world_nether. The radius is limited by max-radius in the config, like other commands.
For example, /co purge t:30d r:50 i:zombie will delete zombie kills older than one month within 50 blocks of you, without removing any other data. Use r:50x10 to also limit the height to 10 blocks above and below you.
Purging Blocks
You can optionally specify block types in CoreProtect v23+.
For example, /co purge t:30d i:stone,dirt will delete all stone and dirt data older than one month, without removing other block data.
Purging Entity Kills
Entity kill logs can be purged separately from other data on SQLite, MySQL, and DuckDB. Each kill also removes the saved entity data used to restore the mob on rollback.
/co purge t:30d a:killdeletes all entity kills older than one month, and keeps all other data./co purge t:30d i:zombie,skeletondeletes zombie and skeleton kills older than one month. Entity and block types can be combined ini:./co purge t:30d e:villager,wolfdeletes all data older than one month, but keeps villager and wolf kills./co purge t:30d a:kill e:villager r:#worlddeletes entity kills older than one month in the overworld, except villager kills.
e: only accepts entity types. It cannot be combined with entity types in i:, or with only block types in i:, because a block restriction already keeps every kill. a:kill cannot be combined with block types in i:.
Database Optimization
In CoreProtect v2.15+, adding #optimize to the end of the command (for example, /co purge t:30d #optimize) will also optimize supported database tables and reclaim unused disk space. How this option is handled depends on the database backend:
- SQLite already rebuilds the database from retained data and reclaims unused file space as part of a manual purge, so
#optimizeis not needed. - MySQL normally deletes matching rows. Adding
#optimizealso removes saved entity data in the purged time range that no entity kill references anymore (this requires theCREATE TEMPORARY TABLESprivilege), then optimizes its tables to reclaim unused space. - DuckDB deletes matching rows in one transaction and checkpoints afterward.
#optimizehas no additional effect. - ClickHouse drops fully covered monthly partitions for an unfiltered time purge and synchronously removes rows from partial or filtered partitions. Adding
#optimizealso runsOPTIMIZE TABLE ... FINAL.
#optimize can significantly slow MySQL and ClickHouse purges and is generally unnecessary.
/co reload
Reloads the configuration file.
/co status
Displays the plugin status and version information.
/co consumer
Console command to pause or resume consumer queue processing.
/co migrate-db
Migrate data from the active database backend to a different backend. This is a console-only command.
| Command | Parameters |
|---|---|
| /co migrate-db | `<sqlite |
By default, migration validates all table statistics and compares approximately 1% of each large history table, with full comparisons for small and reference tables. Add --full-validation to compare every copied row.
The target namespace must contain no CoreProtect data; a DuckDB target must use a new database file, and database-lock must remain enabled. After a successful migration, CoreProtect automatically updates database-type in config.yml before queued writes resume.
Note: Migrations between SQLite and MySQL require a CoreProtect 23.0+ Patreon build. Any migration involving DuckDB or ClickHouse requires CoreProtect 25.0+.
For complete migration instructions, safety guidelines, and troubleshooting information, see the Database Migration documentation.
Parameter Details
u:<user>
You can specify a single user or multiple users.
- Example:
u:Notch - Example:
u:Notch,Intelli - Example:
u:#fire,#tnt,#creeper,#explosion
t:<time>
You can specify weeks, days, hours, minutes, and seconds.
Time amounts can be combined, and decimals may be used.
- Example:
t:2w,5d,7h,2m,10s - Example:
t:5d2h - Example:
t:1h-2h(between one to two hours) - Example:
t:2.50h(two and a half hours)
r:<radius>
A numeric radius targets within that many blocks of your player location.
- Example:
r:10(target within 10 blocks of your location) - Example:
r:#world_the_end(target a specific world) - Example:
r:#global(target the entire server) - Example:
r:#worldeditorr:#we(target a WorldEdit selection)
a:<action>
Restrict the command to a specific action
- Example:
a:+block(only include placed blocks)
Actions
| Action | Description |
|---|---|
a:block |
blocks placed/broken |
a:+block |
blocks placed |
a:-block |
blocks broken |
a:chat |
messages sent in chat |
a:click |
player interactions |
a:command |
commands used |
a:container |
items taken from or put in chests |
a:+container |
items put in chests |
a:-container |
items taken from chests |
a:inventory |
items added or removed from player inventories |
a:+inventory |
items added to player inventories |
a:-inventory |
items removed from player inventories |
a:item |
items dropped, thrown, picked up, deposited, or withdrawn by players |
a:+item |
items picked up or withdrawn by players |
a:-item |
items dropped, thrown, or deposited by players |
a:kill |
mobs/animals killed |
a:spawn |
entities placed or spawned by players |
a:session |
player logins/logouts |
a:+session |
player logins |
a:-session |
player logouts |
a:sign |
messages written on signs |
a:username |
username changes |
i:<include>
Can be used to specify a block/item/entity.
- Example:
i:stone(only include stone) - Example:
i:stone,oak_wood,bedrock(specify multiple blocks)
You can find a list of block names at https://coreprotect.net/wiki-blocks.
You can find a list of entity names at https://coreprotect.net/wiki-entities.
e:<exclude>
Can be used to exclude a block/item/entity/user.
- Example:
e:tnt(exclude TNT)
f:<filter>
Filter message prefixes when using a:chat, a:command, or a:sign.
- Example:
a:command f:/ban(commands starting with "/ban") - Example:
a:command f:/ban,/kick(commands starting with either prefix) - Example:
a:command f:/ban,-/banlist(include "/ban" and exclude "/banlist") - Example:
a:chat f:-hello(exclude messages starting with "hello") - Example:
a:sign f:Diamond Shop,-Diamond Shop Closed(filter prefixes on the recorded sign face)
Separate filters with commas. Included prefixes are alternatives; any matching excluded prefix removes the result. Exclusions can be used on their own. Each prefix requires at least three characters, excluding the leading
-marker. Spaces are allowed, and*,%,_, and~are matched literally.
#<hashtag>
Add a hashtag to the end of your command to perform additional actions.
- Example:
#preview(perform a rollback preview)
Hashtags
| Hashtag | Effect |
|---|---|
#preview |
Preview a rollback/restore |
#count |
Return the number of rows found in a lookup query |
#summary |
Return the row count and per-user material totals |
#verbose |
Display additional information during a rollback/restore |
#silent |
Display minimal information during a rollback/restore |
Example Commands
Example Rollback Commands
By default, if no radius is specified, a radius of 10 will be applied, restricting the rollback to within 10 blocks of you. Use r:#global to do a global rollback.
/co rollback Notch t:1h
(rollback Notch 1 hour (with default radius of 10))/co rollback u:Notch,Intelli t:1h #preview
(PREVIEW rolling back both Notch & Intelli 1 hour (with default radius of 10))/co rollback u:Notch t:23h17m
(rollback Notch 23 hours and 17 minutes (with default radius of 10))/co rollback u:Notch t:1h i:stone
(rollback ONLY stone placed/broken by Notch within the last hour (with default radius of 10))/co rollback u:Notch t:1h i:stone a:-block
(rollback ONLY stone BROKEN by Notch within the last hour (with default radius of 10))/co rollback u:Notch t:1h r:#global e:stone,dirt
(rollback EVERYTHING Notch did in the last hour EXCEPT for stone and dirt placed/broken)/co rollback u:Notch t:1h r:20
(rollback griefing Notch did in the last hour that is within 20 blocks of you)/co rollback u:Notch t:1h r:#nether
(rollback griefing Notch did in the last hour ONLY in the Nether)/co rollback u:Notch t:5m a:inventory
(rollback inventory transactions by Notch in the last 5 minutes)/co rollback t:15m r:30
(rollback everything done in the last 15 minutes by anyone within 30 blocks of you)/co rollback t:15m r:#worldedit
(rollback everything done in the last 15 minutes in a WorldEdit selection)
Example Lookup Commands
Lookup commands are generally the same as rollback commands. The primary difference is that a default radius is not applied to lookups, meaning all lookup commands do a global search by default.
/co lookup i:diamond_ore t:1h a:-block
(lookup all diamond ore mined in the last hour)/co lookup u:Notch t:30m a:chat
(lookup chat messages sent by Notch in the last 30 minutes)/co lookup u:Notch t:3d a:inventory
(lookup inventory transactions by Notch in the last 3 days)/co lookup u:Notch a:login
(lookup all logins ever done by Notch)/co lookup u:Notch a:login
(lookup all logins ever done by Notch)/co lookup u:Notch a:username
(lookup previous usernames used by Notch)/co lookup r:50 t:7d a:block #summary(show the matching row count and material totals for each user within the same area and time range)