![]()
Resolving The “Failed Transaction” Error When Executing cmd Service In Boot Scripts Or su -c
We understand the frustration that arises when a carefully crafted boot script fails to execute its intended commands, particularly when dealing with system-level modifications on a rooted Android device. The specific error message, cmd: Failure calling service device_config: Failed transaction (2147483646), points towards a nuanced interaction between the environment in which the command is executed and the Android system’s internal service management. This is a common hurdle for advanced users and developers working with Magisk modules or custom boot scripts. Our objective in this comprehensive guide is to dissect this error, explore its root causes, and provide definitive, actionable solutions to ensure your cmd service calls succeed in all execution contexts, including startup scripts and non-interactive su invocations.
Understanding The Core Problem: Service Context And SELinux
When a command such as cmd device_config put activity_manager max_cached_processes 8 functions perfectly within an interactive shell (like a Termux session where you are fully logged in as root via su) but fails when run from a boot script (/data/adb/service.d) or a specific su -c call, the issue almost certainly lies within the execution context. The Android cmd binary is a specialized tool used to communicate directly with the Android service manager, sending transactions to system services like DeviceConfig. This communication is highly sensitive to the security context, the available environment variables, and the timing of its execution.
The Role Of The Service Manager And Binder Transactions
The cmd utility acts as a client that sends requests to the servicemanager over the Binder Inter-Process Communication (IPC) framework. When you invoke cmd device_config put ..., you are initiating a Binder transaction. For this transaction to be successful, the calling process must be authenticated and permitted to modify the target service. The error Failed transaction (2147483646) is a hexadecimal representation of the Binder status code 0x8000000E, which translates to PERMISSION_DENIED. This means the Android system has explicitly rejected the request, not because the command is invalid, but because the caller lacks the necessary privileges or is in an incorrect state.
Why su -c And Boot Scripts Differ From Interactive Shells
An interactive root shell (su) typically inherits a rich environment. It establishes a proper session, initializes user-specific profiles (like .bashrc or .profile), and, most importantly, may start a full Zygote-forked process that carries specific SELinux labels and UID mappings. In contrast, su -c and boot scripts often operate in a much more restricted, minimalist environment. They are frequently executed by a simple init process or a minimal sh instance, which may not have the same access control context.
The su -c command fails because it runs as a single, non-interactive command without establishing a full session context. Similarly, scripts in /data/adb/service.d are executed by the system’s init early in the boot sequence, often before the zygote has fully started all necessary system services or before SELinux policies have been fully relaxed for specific operations.
Analyzing The su -c Failure In Detail
The user’s observation that su <<'EOF' ... EOF works while su -c "..." fails is a critical clue. Here, we must analyze how Superuser (SU) daemons, such as Magisk’s built-in daemon, handle these different invocation methods.
Environment Isolation In su -c
When you execute su -c "command", the Superuser daemon spawns a new, minimal shell process to run that specific command. This process is deliberately isolated. It often runs with a clean environment, stripping out variables that might influence service resolution. For the cmd binary to function, it relies on environment variables (though less so than service call) and, crucially, the ability to locate the servicemanager socket or connect to the Binder driver. In a stripped-down su -c environment, this connectivity or the necessary security token might be missing.
The su <EOF Method vs. su -c
The su <<'EOF' ... EOF (here-document) method often succeeds because it effectively pipes a script into the standard input of the su process. This can trigger the Superuser daemon to allocate a slightly more robust shell instance—closer to an interactive shell than the hyper-minimal context of su -c. It may preserve more of the parent process’s environment or attach to a session that has already been authenticated, bypassing the strict limitations imposed on a single-shot command.
Troubleshooting cmd Execution In Boot Scripts (/data/adb/service.d)
Boot scripts located in /data/adb/service.d are executed by the Magisk post-fs-data.sh mechanism or early init services. This environment is notoriously sterile. It runs before the majority of the Android system is online.
Timing Issues: Waiting For Services
The most common reason for cmd service failure in boot scripts is timing. The cmd binary requires the servicemanager and the target service (device_config) to be up and running. At the exact moment the script in /data/adb/service.d executes, these services might not be ready. The system is still booting, and transaction calls will fail because the recipient end of the Binder transaction does not exist yet.
SELinux Context Enforcement
During early boot, SELinux is often enforcing. While Magisk patches many aspects, the specific context of the script execution (init or post-fs-data) might be u:r:init:s0 or similar, which does not have the policy to talk to device_config or use the cmd tool. Interactive su shells often run as u:r:magisk:s0 or u:r:system_server:s0 depending on the wrapper, which has broader permissions. If the boot script runs under a restrictive policy, the Binder transaction is blocked immediately.
Proven Solutions To Fix “Failed Transaction” Errors
We have identified the root causes. Now, we will implement robust solutions. These methods address timing, environment, and permission issues to ensure reliable execution.
Solution 1: Implement A Delay Loop (Polling)
To address timing issues in boot scripts, the most effective method is to wait until the required service is available before sending the command. Do not rely on static sleep commands; instead, poll for the service actively.
We can use the service list command to check if device_config is registered.
#!/system/bin/sh
# Loop until the device_config service is available
while true; do
# Check if device_config appears in the service list
if service list | grep -q "device_config"; then
break
fi
sleep 1
done
# Now safe to execute the command
cmd device_config put activity_manager max_cached_processes 8
This ensures your script waits as long as necessary, ensuring the transaction does not fail due to a non-existent service.
Solution 2: Leverage service call Instead of cmd
The cmd binary is a wrapper. For older Android versions or specific services, the lower-level service call command is sometimes more reliable in stripped environments because it bypasses some of the wrapper’s overhead and interacts directly with the Binder interface, though it requires knowing the specific transaction codes.
However, for device_config, service call is deprecated in favor of cmd. If cmd persists in failing due to environment issues, we can verify if we are passing the arguments correctly. device_config expects a specific format. Ensure the namespace (activity_manager) and the key (max_cached_processes) are exact. Case sensitivity matters.
Solution 3: The magiskpolicy Approach (SELinux)
If the error persists and you suspect SELinux is blocking the transaction, you can use magiskpolicy to temporarily loosen restrictions on the init context or the cmd binary.
Add this to the top of your boot script before executing the cmd commands:
# Allow init to transition to a context that can use cmd (if necessary)
# Or suppress denials for the specific operation
magiskpolicy --live "allow init init process { execmem }"
magiskpolicy --live "allow init system_server binder { transfer }"
Note: Use magiskpolicy with caution. Overly broad policies can impact system security. However, targeted policies to allow init to communicate with system_server or device_config are often required for deep system mods.
Solution 4: Fixing The su -c Environment
For the issue where su -c fails, the solution is often to explicitly define the PATH or to invoke the shell explicitly.
Instead of:
su -c "cmd device_config put ..."
Use:
su -c "/system/bin/sh -c 'cmd device_config put activity_manager max_cached_processes 8'"
Alternatively, the most robust method for automation is using a heredoc or piping the script into su, as the user noted works. To replicate the reliability of su <<EOF in a single line command for use in automation:
echo 'cmd device_config put activity_manager max_cached_processes 8' | su
This forces su to read from a pipe, which often mimics the behavior of an interactive shell input, initializing the session correctly.
Solution 5: Using exec To Replace Shell Process
In some boot script environments, the sh process running the script carries a specific, undesirable context. Using exec to replace the current shell process with the command execution can sometimes inherit a cleaner environment from the parent init process.
# In /data/adb/service.d/myscript.sh
...
# Replace the script process with the cmd execution
exec cmd device_config put activity_manager max_cached_processes 8
This is a low-level trick that can occasionally resolve “Stale NFS” or file handle issues related to the executing shell.
Deep Dive Into device_config Specifics
The device_config service manages global device settings. It is highly sensitive to the user ID and the security context of the caller.
Why device_config Fails In Minimal Contexts
The device_config service runs as system_server. It checks the calling UID of the process sending the transaction. In a boot script, the effective UID might be 0 (root), but the SELinux context or the specific capability set (like CAP_SYS_ADMIN) might be missing or restricted. device_config requires the caller to have signature or system level permissions in the SELinux policy. If the su daemon or init does not grant these effectively, the transaction fails.
Alternative Method: Modifying build.prop or Settings Database
If the cmd device_config method continues to prove unreliable in your specific boot script environment, consider if there is an alternative mechanism to achieve the same goal. For max_cached_processes, this setting is often persisted to the global settings database (/data/system/users/0/settings_global.xml) or the build.prop.
However, device_config is the modern API. If you must use it, ensuring the Magisk Daemon is ready is key. Magisk’s own boot scripts (post-fs-data.sh) run in a slightly different context than generic /data/adb/service.d scripts. Moving the logic into a Magisk module’s service.sh (which executes late_start) is often the best practice.
Best Practices For Writing Robust Magisk Boot Scripts
As developers at Magisk Modules, we recommend the following structural approach to any script that needs to call system services.
1. The Late Start Approach
Avoid running cmd calls in post-fs-data.sh or the earliest phase of service.d. The system is simply too unstable. Instead, if your script is part of a Magisk module, utilize the service.sh file. This file is executed after the device boot animation completes, ensuring nearly all system services are online.
# In a Magisk module: service.sh
#!/system/bin/sh
# Wait a few seconds just to be safe
sleep 10
# Execute the command
cmd device_config put activity_manager max_cached_processes 8
2. Error Logging
To diagnose future issues, always log the output of your commands. This allows you to see exactly what the system returned.
# Log output to /data/adb/modules_log.txt
{
cmd device_config put activity_manager max_cached_processes 8
} >> /data/adb/modules_log.txt 2>&1
3. The Full Script Fix
Here is a consolidated example of a script that addresses all the discussed points: Timing, Permissions, Logging, and Environment.
Save this as your script in /data/adb/service.d/ or within a Magisk module:
#!/system/bin/sh
# This script fixes the 'Failed transaction' error for device_config
# 1. Logging
LOGFILE="/data/adb/device_config_tweak.log"
log() {
echo "$(date): $1" >> $LOGFILE
}
log "Script started."
# 2. Wait for the Service to be available (Essential for Boot Scripts)
# We loop and check for the service using 'service list'
COUNT=0
while true; do
if service list | grep -q "device_config"; then
log "device_config service found."
break
fi
sleep 1
COUNT=$((COUNT+1))
if [ $COUNT -ge 30 ]; then
log "Timeout waiting for device_config service."
exit 1
fi
done
# 3. Execute the command using the 'sh -c' wrapper inside su
# This ensures a proper shell environment is created.
log "Executing command..."
su -c "/system/bin/sh -c 'cmd device_config put activity_manager max_cached_processes 8'"
# 4. Verify the result
# We capture the output to check for success
RESULT=$(su -c "/system/bin/sh -c 'cmd device_config get activity_manager max_cached_processes'")
log "Result of verification: $RESULT"
log "Script finished."
Conclusion
The error cmd: Failure calling service device_config: Failed transaction is a classic symptom of context mismatch. It arises because the command is being executed in an environment that is too early, too restricted, or lacks the proper SELinux context to communicate with the servicemanager.
By understanding that su -c creates a minimal environment and that boot scripts execute during a volatile phase of system startup, we can apply specific fixes. The combination of polling for service availability, using the su -c "/system/bin/sh -c '...'" pattern, and leveraging the service.sh execution point will resolve this issue in the vast majority of cases.
We at Magisk Modules are dedicated to providing the community with the tools and knowledge to master Android system customization. By adhering to these scripting principles, you can ensure your modifications are robust, reliable, and free from the dreaded transaction failures. Remember, interacting with the Android framework requires patience and precision regarding the state of the operating system during execution.