A seemingly small OpenClaw error can occasionally expose several layers of state that have accumulated over time: stale Codex session bindings, retired model references, legacy media metadata, database schema migrations, old plugin generations, and outdated systemd service definitions.
This article documents one such recovery on an Ubuntu host. All hostnames, usernames, domains, IP addresses, ports, account identifiers, session identifiers, and personally identifying paths have been removed or replaced with generic placeholders.
The final result was a healthy OpenClaw installation running version 2026.9.3, with a valid schema-16 database, working Gateway, current plugins, corrected systemd configuration, and no recurrence of the original Codex runner conflict.
The Initial Failure
The incident started with an OpenClaw task immediately failing with:
Codex session became active in another runner;
wait for it to finish before continuing
The same workload had been functioning normally before the failure appeared.
At the time, the installation was approximately:
OpenClaw 2026.9.1
Node.js v24.x
Ubuntu 24.04 LTS
Restarting the entire operating system did not solve the problem.
After reboot, the Gateway itself looked healthy:
Runtime: running
Connectivity probe: ok
That indicated that the failure was not simply a dead Gateway process or an abandoned operating-system process.
The problem was instead related to OpenClaw’s persistent session and state layer.
Attempting an OpenClaw Upgrade
Because newer OpenClaw releases contained additional Codex/session handling improvements, the next logical step was to upgrade from:
2026.9.1
to:
2026.9.3
The normal upgrade command was started:
openclaw update
The package-manager phase succeeded, but the Doctor phase stopped during state migration.
The important error was:
Failed reading legacy managed outgoing image state:
Error: legacy managed image original is outside managed outgoing storage
This was not a Node.js installation failure, network failure, or package-manager failure.
It was a legacy-state migration problem.
Legacy Managed-Image Metadata
Inspection showed that OpenClaw still had old JSON records representing managed outgoing images.
A typical record contained metadata similar to:
{
"attachmentId": "<UUID>",
"sessionKey": "<SESSION>",
"original": {
"path": "/home/<OLD_USER>/.openclaw/media/outgoing/originals/<UUID>.png"
}
}
The current OpenClaw installation, however, was running under a different state location:
<OPENCLAW_STATE_DIR>/media/outgoing/originals/
Most of the old records pointed to files under an obsolete user’s home directory.
Further inspection established that:
37 records -> obsolete paths, original image already missing
3 records -> valid current paths, original image still present
OpenClaw’s migration code intentionally rejected the invalid records rather than silently importing metadata referencing files outside the managed storage root.
That behavior is conservative but correct: state migration should fail closed rather than silently corrupt attachment ownership.
Quarantining Only Proven-Orphaned Records
Rather than deleting the entire media state or modifying SQLite manually, only the records that met all of the following conditions were removed from active migration input:
original path outside the current managed-media root
AND
referenced original file does not exist
AND
no corresponding current managed file exists
Before making the change, the complete legacy-record directory was copied to a temporary recovery location.
The invalid records were then moved into a quarantine directory, leaving the three valid records untouched.
The result was:
Moved orphaned : 37
Kept valid : 3
Need attention : 0
No active database rows or current attachments were deleted.
An Interrupted Upgrade Created a Schema-Version Mismatch
The next complication was more interesting.
During the failed update, OpenClaw had already migrated its shared SQLite database from:
schema 15
to:
schema 16
But because Doctor later failed, the updater restored the installed OpenClaw package to version 2026.9.1.
The result was:
Database schema: 16
Installed OpenClaw supports: 15
OpenClaw correctly refused to start against a database newer than the binary understood:
This OpenClaw build cannot open your existing data.
OpenClaw state database uses newer schema version 16;
this build supports 15.
At this point, repeatedly running:
openclaw update
was no longer useful because the old binary could not even safely open the current state database.
The correct recovery direction was forward, not backward.
Manually Installing the Compatible OpenClaw Version
The compatible release was installed directly with npm while the Gateway remained stopped.
Conceptually:
npm install -g openclaw@2026.9.3
After installation:
openclaw --version
reported:
OpenClaw 2026.9.3
This restored binary/database compatibility.
The next step was:
openclaw doctor --fix
Doctor successfully migrated the three valid managed outgoing image records:
Migrated 3 managed outgoing image record(s) → shared SQLite state
Removed legacy managed outgoing image JSON after SQLite verification
That confirmed that the earlier image-state problem had been resolved correctly.
A Second Migration Blocker: Skill Workshop
Doctor then encountered another legacy-state problem:
Failed to migrate Skill Workshop proposal:
proposal draft hash does not match proposal metadata
An old Skill Workshop proposal contained:
PROPOSAL.md
proposal.json
rollback.json
but the stored proposal metadata did not match the current draft hash.
Because the proposal could not be verified safely, Doctor refused to continue migrating state.
The affected legacy proposal was removed from the active proposal-discovery location and placed into a recovery directory instead of editing its metadata or fabricating a matching hash.
Afterward, Doctor was run again.
This time it completed:
Doctor complete.
The temporary recovery copy was later deleted after the installation had been verified healthy and the old proposal was no longer required.
The Important Codex Repair
During the successful Doctor run, one line was especially relevant to the original problem:
Removed 1 orphaned Codex app-server session binding(s)
This was the strongest direct indication that persistent Codex session state had contributed to the original:
Codex session became active in another runner
failure.
A reboot alone had not solved the problem because the bad relationship was not merely an in-memory process lock. It existed in persistent OpenClaw state.
Doctor repaired that state.
Model Migration: gpt-5.4-mini to gpt-5.6-luna
There was also a second independent issue visible in the old Gateway logs.
Requests using the former default model produced:
The 'gpt-5.4-mini' model is not supported when using Codex with a ChatGPT account.
The old primary model had been:
openai/gpt-5.4-mini
OpenClaw 2026.9.3 automatically migrated the default to:
openai/gpt-5.6-luna
with a fallback such as:
deepseek/deepseek-v4-flash
Doctor explicitly reported the migration:
Replaced retired agents.defaults.model.primary
"openai/gpt-5.4-mini"
with
"openai/gpt-5.6-luna".
Residual active references to gpt-5.4-mini were subsequently removed.
The resulting model status was approximately:
Default:
openai/gpt-5.6-luna
Fallback:
deepseek/deepseek-v4-flash
OpenAI runtime:
Codex
authentication usable
no route issues
A separate openai/gpt-5.4 alias remained in configuration, but it was not the retired gpt-5.4-mini model and was not the active default.
Plugin Version Alignment
Doctor initially warned that several official plugins were still associated with the previous OpenClaw generation.
After repair and restart, the important enabled plugins were verified as:
Codex 2026.9.3
DeepSeek 2026.9.3
DuckDuckGo 2026.9.3
Nextcloud Talk 2026.9.3
There were no remaining 2026.9.1 versions among those active plugins.
This mattered because running a newer core with stale managed plugin generations could otherwise introduce confusing compatibility failures.
Fixing the systemd Gateway Service
The upgraded OpenClaw binary also detected that its systemd unit was outdated.
The old service contained:
KillMode=control-group
OpenClaw 2026.9.3 required:
KillMode=mixed
The reason is operationally important.
With KillMode=mixed, OpenClaw can first allow active work to drain cleanly before systemd performs final child-process cleanup.
The service was regenerated using OpenClaw’s supported installer:
openclaw gateway install --force
Afterward:
systemctl --user show openclaw-gateway.service -p KillMode
returned:
KillMode=mixed
An existing memory-limit drop-in was preserved:
[Service]
MemoryHigh=5G
MemoryMax=6G
This demonstrated why operator-owned systemd drop-ins are preferable to manually editing generated service units.
Why Gateway Restart Initially Appeared to Fail
Starting or restarting the Gateway sometimes produced:
Gateway start timed out after 45s waiting for /healthz and /readyz
This looked alarming but was not actually a failed start.
Logs showed approximately:
systemd service started
...
Gateway loading configuration
...
database opened
...
HTTP server listening
...
Gateway ready
The host simply needed roughly one minute to become fully ready, while the CLI waited only 45 seconds.
The actual runtime state afterward was:
Runtime: running
Connectivity probe: ok
So the timeout was a readiness-wait timeout, not a service crash.
Controlled Restart and Drain Behavior
Once KillMode=mixed was active, a later Gateway restart entered a controlled drain:
pendingReplies=1
embeddedRuns=1
During this phase, temporary HTTP 503 responses and transport loss were expected.
Eventually the client reported:
gateway reconnected after transport loss
A subsequent inspection showed a new Gateway PID, zero restart-loop count, and healthy runtime state.
This confirmed that the managed restart mechanism was functioning correctly.
Database Verification
After all migrations and restarts, the shared SQLite database was checked directly in read-only mode.
Schema:
PRAGMA user_version;
Result:
16
Integrity:
PRAGMA integrity_check;
Result:
ok
This was an important final check because the recovery had crossed a database migration boundary.
No attempt was made to manually change SQLite schema-version metadata.
Verifying the Original Error Did Not Return
The most useful log check was not to search the entire historical log indiscriminately.
Instead, logs were scoped to the startup time of the new Gateway process.
Only events after the repaired Gateway started were examined for:
Codex session became active in another runner
model_not_found
orphaned binding
schema errors
database failures
plugin failures
fatal
panic
crash
No new occurrences of the original Codex runner error appeared.
The new process showed normal activity such as:
sessions.list
chat.history
models.authStatus
talk.catalog
with successful responses.
The old active in another runner entries remained in historical logs, but they belonged to the previous Gateway process and were therefore not evidence of a current fault.
This distinction between historical errors and current errors is essential during long-running troubleshooting sessions.
Backup Hygiene
The repair also exposed an important operational issue: automatically generated backup names do not always match a desired archival convention.
The preferred long-term convention was:
original-filename.YYYYMMDD-HHMMSS
Examples:
openclaw.json.20260909-123456
openclaw-gateway.service.20260909-123456
Names such as:
*.bak
*.old
*.orig
*.save
were not considered valid long-term archive names.
When OpenClaw generated a .bak file, it was compared against an existing timestamped backup using SHA-256.
If both files were byte-for-byte identical, the non-standard duplicate could be removed safely while retaining the timestamped archive.
Temporary diagnostic and recovery data created during troubleshooting were also removed after successful recovery, including temporary migration copies and support/triage artifacts that were no longer needed.
Historical timestamped backups were preserved.
Final State
The repaired installation ended in approximately the following state:
OpenClaw: 2026.9.3
Node.js: 24.x
SQLite schema: 16
SQLite integrity: ok
Gateway:
active
running
connectivity probe: ok
systemd:
KillMode=mixed
MemoryHigh=5G
MemoryMax=6G
Default model:
openai/gpt-5.6-luna
Fallback:
deepseek/deepseek-v4-flash
Key plugins:
Codex 2026.9.3
DeepSeek 2026.9.3
DuckDuckGo 2026.9.3
Nextcloud Talk 2026.9.3
Codex:
orphaned app-server binding repaired
no new "active in another runner" errors observed
Lessons From the Incident
The main lesson was that several superficially unrelated errors were actually consequences of persistent state accumulated across OpenClaw versions.
The original Codex error was not solved by rebooting because at least part of the problem existed in persisted application state rather than merely in running processes.
The upgrade then exposed old media metadata and a malformed Skill Workshop proposal because the newer Doctor performed stricter migrations.
The failed migration temporarily left the database newer than the installed binary, making a forward manual package installation necessary.
Once the correct binary was installed, the migration blockers were isolated rather than destructively removed, Doctor completed, the orphaned Codex binding was repaired, retired model configuration was migrated, and the systemd service definition was brought in line with the current release.
The broader operational principle is simple:
Repair the smallest proven fault, preserve recoverable state, distinguish historical errors from current ones, and stop troubleshooting once the service is demonstrably healthy.
A working system does not become healthier merely because more commands are run against it.