Forum teuk.org

πŸ§™β€β™‚οΈπŸ§Ή Mediabot v3: Claude Ghost Cleanup and Passive DCC Revival

in Mediabot Β· started by TeuK Β· 2w ago

TeuK Β· 2w ago

This one is a cleanup commit, but an important one.

It fixes a real Claude integration issue, removes a dangerous duplicate module trap, and makes DCC CHAT passive usable with modern IRC clients.

No database schema change.

No migration.

No table touched.

Just code, tests, and repository hygiene.


🧭 Why this pass was needed

During the Claude integration audit, we found two separate problems that looked unrelated but both had the same root smell:

state that looked correct from the outside, but was not actually clean inside

One problem was in the Claude session state.

The other was in the repository layout itself.

There was also a DCC CHAT parsing issue that made passive DCC fail for modern clients using non-numeric tokens.

This pass cleans all of that before moving forward.


πŸ‘» The phantom Claude module

The repository had two files declaring the same package:

Mediabot/External/Claude.pm
Mediabot/Claude.pm

Both declared:

package Mediabot::External::Claude;

But Perl resolves:

require Mediabot::External::Claude;

to the canonical path:

Mediabot/External/Claude.pm

not:

Mediabot/Claude.pm

That made Mediabot/Claude.pm an orphan duplicate.

This is the worst kind of file in a codebase: it looks real, can be edited, can receive fixes, and yet production never loads it.

So the cleanup is simple and correct:

Mediabot/Claude.pm is removed from the repository
Mediabot/External/Claude.pm remains the only canonical Claude module

A new regression test now prevents that duplicate from coming back.


🧠 !ai forget now really forgets

The !ai forget command promised to clear Claude history and persona.

But the Claude session state had grown over time.

The command only cleared:

_claude_history
_claude_persona

It did not clear:

_claude_pinned
_ai_last_active

That meant a user could run:

!ai forget

and still keep a pinned context active.

That is misleading.

The command now clears all relevant per-user/per-channel Claude state:

_claude_history
_claude_persona
_claude_pinned
_ai_last_active

It also keeps the existing key conventions straight:

history       -> raw nick
persona       -> lower-case nick
pinned        -> lower-case nick
last_active   -> lower-case nick

The user-facing message now reflects the truth:

Your Claude history, persona and pinned context have been cleared.

That is better than pretending only history and persona exist.


πŸ” Claude history rollback after API failure

The second Claude bug was nastier.

When claudeAI() sends a prompt, it pushes a user message into the in-memory history before calling the API.

If the API call succeeds, an assistant response is appended and the history remains valid:

user
assistant
user
assistant

But if the API fails before an assistant response is appended, the last user message remains orphaned:

user
assistant
user

The next prompt adds another user message:

user
assistant
user
user

That violates the expected user/assistant alternation and can cause the API to reject the conversation until the state is cleared.

The fix is precise:

if the API result is undef
and the last history role is still user
then pop that orphan user message

Cache hits are preserved because they append an assistant answer internally before returning.

So the code distinguishes:

API failure       -> last role is user      -> rollback
cache hit success -> last role is assistant -> keep

That makes transient API failures recoverable instead of poisoning the session.


πŸ“ž Passive DCC CHAT works with modern tokens

The DCC parser expected passive DCC tokens to be numeric:

(\d+)

and _handle_dcc_chat_request also required:

$token =~ /^\d+$/

That is too strict for modern IRC clients.

Passive DCC tokens are opaque identifiers. Clients may send values like:

1234567890abcdef
f7e3a4b9-c8d2-e1f0
abc.def_123-XYZ

The token is not evaluated.

It is not passed to a shell.

It is just compared and logged as an opaque string.

So the accepted safe format is now:

[A-Za-z0-9._-]+

This preserves numeric compatibility while supporting hex, UUID-like, and mixed safe tokens.

Dangerous characters remain rejected:

;
space
|
$
<

So this is broader without becoming sloppy.


πŸ§ͺ Regression tests added and cleaned

This pass keeps the recent habit of making tests directly executable.

The Claude test validates:

!ai forget clears all four Claude session caches
other users' state remains untouched
private context uses the private key correctly
API failure rolls back orphan user messages
cache hit does not trigger rollback
history remains alternating after failure recovery

The DCC test validates:

numeric passive token still works
hex token works
UUID-like token works
mixed dot/underscore/dash token works
unsafe token characters are rejected
active DCC CHAT remains unchanged
Mediabot.pm no longer requires numeric-only passive tokens

The new duplicate-module test validates:

only one file declares package Mediabot::External::Claude
that file is Mediabot/External/Claude.pm
Mediabot/Claude.pm is absent
External.pm requires the canonical module

This prevents the exact mistake from returning later.


βœ… Validation set

Recommended checks:

cd /home/mediabot/mediabot_v3 || exit 1

perl -I. -c Mediabot/External/Claude.pm
perl -I. -c Mediabot/External.pm
perl -I. -c Mediabot/DCC.pm
perl -I. -c Mediabot/Mediabot.pm

perl -c t/cases/391_mb141_claude_forget_and_history_rollback.t
perl -c t/cases/392_mb142_dcc_token_alphanumeric.t
perl -c t/cases/395_mb144_no_orphan_claude_duplicate.t

perl t/cases/391_mb141_claude_forget_and_history_rollback.t
perl t/cases/392_mb142_dcc_token_alphanumeric.t
perl t/cases/395_mb144_no_orphan_claude_duplicate.t

git diff --check

Expected direct test coverage:

Claude forget/history rollback     26 assertions
DCC passive token handling          26 assertions
Claude duplicate module guard        5 assertions

🧯 Operational impact

The Claude fix prevents confusing sticky state after !ai forget.

The history rollback fix prevents an API failure from poisoning the conversation until manual cleanup.

The DCC fix makes passive DCC CHAT work with modern client tokens, especially useful behind NAT/firewall situations.

The module cleanup removes a repository trap that could silently swallow future fixes.

This is the kind of cleanup that saves time later.


πŸ•―οΈ Final note

The best bugfixes are not always the loudest ones.

Sometimes the important work is removing ghosts.

This pass removes a phantom Claude module, makes !ai forget honest, keeps Claude history balanced after failures, and revives passive DCC CHAT for modern clients.

A cleaner castle.

Fewer ghosts.

Better spells.

πŸ§™β€β™‚οΈπŸ§ΉπŸ“ž

You must be logged in to reply.