The problems is not in build_context_paths. (I mistakenly thought it might do a truncate table and rebuild, but it does not.)
cleanup_contexts in accesslib.php (called from cron and upgrade) correctly uses delete_context - and this should clean up
Ah, delete_context was missing the cleanup of role_names.
Is delete_context always called when it needs to be?
- system: should never be deleted.
- course category deleted: called from course/lib.php.
- user deleted: called from delete_user in lib/moodlelib.php.
- course deleted: called from delete_course in lib/moodlelib.php.
- module deleted:
+ called from remove_course_contents in lib/moodlelib.php when a course is emptied (either by delete course of restore deleting existing).
+ ERROR delete_course_module in course/lib.php does not call delete_context.
+ ERROR admin/modules.php does not it when a module is uninstalled.
+ possible error: backup/restorelib.php does delete_records('course_modules' without cleaning up the context.
- block deleted:
+ called from remove_course_contents in lib/moodlelib.php when a course is emptied (either by delete course of restore deleting existing).
+ ERROR blocks_delete_instance (lib/blocklib.php) does not call delete_context.
+ ERROR restore_create_blocks does delete_records('block_instance', ... without proper cleanup.
+ ERROR blocks_repopulate_page (lib/blocklib.php) does delete_records('block_instance', ... without proper cleanup
+ ERROR chat|lesson|quiz_delete_instance in mod/chat|lesson|quiz/lib.php do delete_records('block_instance', ... without proper cleanups.
For the blocks stuff, looking at the problematic code, a blocks_delete_all_on_page($pagetype, $pageid) to replace the delete_records call is probably the easiest solution.
I don't really understand why these errors leave bad data related to contexts floating around, however, since cleanup_contexts, called from cron, should get rid of them, and anyway the issues is to do with contexts being deleted while related data remains.
And the only place in the code we seem to delete records from the context table (checked for with regex delete_record.*context) is in the delete context function.
Work out where these are coming from, and see if we can devise an automated clean-up on upgrade.