MUSH / MUX with PennMUSH
PennMUSH softcode changes traditionally propagate immediately across all connected players, risking cascading failures from syntax errors or logic conflicts. This guide establishes a sandboxed testing architecture using zone-based isolation, @hook-driven audit logging, and atomic deployment strategies that mirror modern CI/CD practices without external tooling dependencies.

Architect the Parent Object Hierarchy for Environment Isolation
Create distinct @parent object trees for development, staging, and production environments. The production parent should carry the live softcode, while development parents inherit from a sandbox zone (#5) with restricted command permissions. Use @lock/enter and @lock/use to prevent unauthorized promotion of objects between zones.
@create Development Parent
@set Development Parent=SAFE
@parent Development Parent=#5
@lock Development Parent=me
@create Production Parent
@set Production Parent=SAFE
@parent Production Parent=#1⚠ Common Pitfalls
- •Avoid placing @hook commands directly on room objects as zone inheritance may cascade unexpectedly
- •Do not use the same dbref for parent objects across different MUSH instances without updating @parent references
Implement @hook Logging for All Softcode Modifications
Configure @hook/after on critical command objects (particularly @set, @cpattr, and @edit) to write timestamped entries to an Audit Object. This object stores the previous attribute value, the new value, the executor dbref, and the Unix timestamp in a structured list format parseable by external scripts.
&AUDIT_LOG Audit Object=[setq(0,time())][setq(1,lastcreate(me,ATTR))][setq(2,get(%!/lastobject))][setq(3,edit(%0,\,\%b,\,))][setq(4,get(%#/%1))]insert(v(0),1,\%t|%q0|%#|%q2|%q1|%q4|%q3)
@hook/after @set object=Audit Object/audit_log⚠ Common Pitfalls
- •@hook can create infinite loops if the hook target modifies the attribute that triggered it
- •Large attribute values may exceed the buffer limit when concatenated with metadata
Build the Sandboxed Testing Command Parser
Develop a user command that clones objects into the development zone, applies proposed softcode changes, and runs assert() checks against known good inputs. The parser must capture error messages from the MUSH parser using @assert and @break, logging failures without affecting production objects.
&CMD_TEST Object=$test *:@create Test Clone
@lock Test Clone=%#
@parent Test Clone=[loc(me)]
@cpattr %0/%1=Test Clone/%1
@assert hasattr(Test Clone,%1)={@pemit %#=FAIL: Attribute copy error}
@trigger me/test_runner=%#,Test Clone,%1⚠ Common Pitfalls
- •Cloned objects retain the original creation timestamp which may confuse time-based softcode
- •The default quota system may block automated object creation without @power QUOTA
Create Atomic Deployment with @restore Checkpoints
Before deploying to production, use @backup to create a flatfile checkpoint. Implement a two-phase commit: first @cpattr the new softcode to a temporary holding attribute on the production object, then @switch to verify syntax validity before moving to the primary attribute slot. If validation fails, @restore from the checkpoint file immediately.
&CMD_DEPLOY Object=$deploy * *:@cpattr %0/%1=%0/_NEW_%1
@assert isdbref(%0)={@pemit %#=ERROR: Invalid object}
@assert hasattr(%0,_NEW_%1)={@pemit %#=ERROR: Copy failed}
@switch isnum(get_eval(%0/_NEW_%1))=1,{&%1 %0=[get(%0/_NEW_%1)] @pemit %#=DEPLOYED},{@pemit %#=SYNTAX ERROR: Check _NEW_%1}⚠ Common Pitfalls
- •@backup requires the flatfile to be writable by the MUSH process user
- •Concurrent deployments may race condition on the _NEW_%1 attribute; implement a lock attribute
Establish Regression Testing with Known-State Objects
Maintain a library of test objects with predetermined attribute states representing edge cases (empty strings, special characters, nested functions). After each deployment, run a softcoded test suite that compares expected outputs against actual get() results, flagging discrepancies to a dedicated admin channel.
&TEST_SUITE Quality Object=@dolist v(test_cases)={@assert [get(%i0/expected)]=[get_eval(%i0/code)]={@pemit %ch=REGRESSION FAIL: %i0}}
&test_cases Quality Object=#123 #124 #125⚠ Common Pitfalls
- •Test objects must be excluded from @purge commands to prevent accidental deletion
- •Time-based functions like time() or secs() will fail deterministic tests; mock these with stored values
Configure Automated Rollback Triggers
Implement a watchdog object that monitors error rates via @hook on @assert failures. If error frequency exceeds threshold within a 5-minute window, trigger @restore from the pre-deployment checkpoint and notify admin channel. Store rollback metadata in a persistent attribute for post-mortem analysis.
&WATCHDOG Monitor Object=@hook/after @assert fail=Monitor Object/record_error
&record_error Monitor Object=[setq(0,add(v(error_count),1))]set(me,error_count:%q0)[ifelse(gt(%q0,5),{@trigger me/emergency_rollback})]
&emergency_rollback Monitor Object=@restore mush/game.db.prev; @shutdown/reboot⚠ Common Pitfalls
- •@shutdown/reboot disconnects all players; use @restart instead for softcode-only reloads if available
- •Rollback loops may occur if the restored softcode contains the same error; implement a max_retry counter
What you built
This architecture creates a defensive barrier between development experimentation and player-facing systems. By treating softcode changes as atomic transactions with rollback capability, you eliminate the risk of extended downtime from syntax errors. Monitor the Audit Object weekly to identify frequently modified attributes that may require refactoring into parent objects to reduce technical debt.