run-as
read-onlyRuns a command as one app's own uid — the way to read a debug build's private files without root.
- Command
- adb shell run-as
- Commands
- System and logs
- Risk
- read-only
Syntax
run-as <package> <command> [<args>]Flags and arguments
| Token | Meaning |
|---|---|
| <package> | The app to borrow the identity of. It has to be installed and marked debuggable in its manifest. |
| --user <id> | Which Android user's copy of the package to become. Without it the helper uses the calling user, which over an ordinary cable is the primary one; pass it explicitly when the same package also exists inside a work profile. |
| <command> | Everything after the package name is executed as the app, with no shell in between unless you ask for one. A redirection written on the same line is therefore performed by *your* shell under the `shell` uid, not by the app — which is exactly why writing the app's database out to shared storage works. |
Examples
run-as com.example.debug idThe probe: it prints the uid it actually became, so the answer is verifiable rather than merely silent.
run-as com.example.debug ls databases/List the app's SQLite files. The working directory is already the app's home.
run-as com.example.debug cat databases/app.db > /sdcard/app.dbCopy a database somewhere you can pull it from.
run-as --user 0 com.example.debug idThe same probe aimed at one named Android user, for a package that also exists inside a work profile.
run-as com.example.debug sh -c 'ls -l files cache'One shell running inside the app's identity, for when a single command is not enough. Quote the whole thing so your own shell hands it over untouched.
Reading the output
Whatever the command you ran prints. A refusal comes from `run-as` itself and says which of three things went wrong: the package is not debuggable, the package is unknown, or the helper is not on this build at all.
Output fields
| Field | Meaning |
|---|---|
| uid=10234(u0_a234) … | The `id` probe succeeded: you are the app now. |
| …is not debuggable | A release build. Nothing to be done without root — this is the platform working. |
| unknown package | A typo, or the app is not installed for this user. |
| run-as: not found | The build ships no helper. Rare, but it happens. |
| package not an application | The name is known to the platform but is not an installed app, so there is no uid to borrow. |
| run-as is disabled from the kernel commandline | The helper was switched off when this image booted. A property of the build, not of the request. |
What access it needs
Special, and worth reading before the failure lines make any sense. `/system/bin/run-as` is a setuid-root helper: it starts privileged, checks who is asking and what they asked for, then drops to the target app's uid and gid before executing your command. Three gates decide whether that happens, and reading the platform source they are unchanged from Android 11 through Android 15. The caller has to be `shell` or `root` — no other uid gets past the first check, which is why this cannot be smuggled into an app. The build must not have the helper disabled from the kernel command line. And the package has to be installed for the Android user you named and marked debuggable in its own manifest, which in practice means a debug build of something you compiled yourself. Having switched uid, it then re-validates the app's data directory before entering it: the path must be absolute, owned by that app's uid and gid, not readable or writable by others, and free of any `..` component. Any of those failing aborts before your command runs at all. So this needs no root, no `adb root` and nothing enabled in developer options — and equally it cannot be talked into a release build. A phone where you need that is a phone that needs root, and no flag on this page substitutes for it.
Failure messages, decoded
| Message | Meaning |
|---|---|
| run-as: package not debuggable: com.example | The app's manifest does not declare itself debuggable. This is the platform working as designed rather than a defect: a store build is refused on every phone, rooted or not. Note the exact wording — Android 11 through 15 all print `package not debuggable`, while much older builds phrased the same refusal as a sentence about the package being not debuggable, so anything matching on the old phrasing will misfile a modern refusal as unrecognised. |
| run-as: unknown package: com.example | The package list held no such name. A typo, or the app is installed for a different Android user than the one being asked about — `pm list packages` settles which of the two it is, and it settles it from the platform rather than from memory. |
| run-as: only 'shell' or 'root' users can run this program | The calling uid is neither. Seen when the command is invoked from inside an app rather than from a cable; the helper checks its caller before it looks at the package at all, so this line tells you nothing about whether the package exists. |
| run-as: package not an application: com.example | The name resolved to something the platform knows about that is not an installed app, so there is no app uid available to become. Distinct from `unknown package`, and the distinction is the useful part. |
| run-as: run-as is disabled from the kernel commandline | This build was booted with the helper switched off. Nothing about the app, the package name or your own uid changes the answer; it is a property of that boot, and the only route past it is a different image. |
| run-as: couldn't chdir to package's data directory '/data/user/0/com.example': Permission denied | The uid switch succeeded and entering the app's home did not. Uncommon, and it usually means the data directory's ownership or mode failed one of the checks the helper performs immediately before this step — so read it as a statement about the directory on disk rather than about your access. |
Version and vendor notes
- This works only on an app whose manifest declares it debuggable, which in practice means a debug build of your own app. A Play Store app is refused, and that refusal is the security model rather than a bug.
- The command starts in the app's home directory, so use relative paths like `databases/app.db`. There is no way to point it at another app's directory.
- Copying a database while the app has it open can capture a half-written state. Stop the app first if the contents matter.
- The gates are checked in a fixed order — the calling uid, then the kernel-command-line switch, then the package itself — so the first line that comes back tells you which one you are standing at, and there is no point arguing with a later one until the earlier one passes.
- After switching uid the helper re-validates the app's data directory: absolute, correctly owned, not group- or world-accessible, and containing no `..`. A refusal at that stage describes the directory on disk rather than your own access, and it aborts before your command runs.
We have a UI for this
Android ADB Toolbox runs this command over USB and parses what comes back — a Chromium browser, no SDK, nothing uploaded.
Related commands
Commands that turn up in the same session as run-as.
| Command | What it does |
|---|---|
| logcat | Streams the system log: the running commentary of everything happening on the phone. |
| dumpsys dropbox | The platform's ring buffer of crashes, ANRs and tombstones — how you read ANR traces without root. |
| pm path | Prints the absolute paths of the APK files that make up an installed package. |
More System and logs commands
Checked against AOSP, toybox and kernel sources while writing the parsers behind our Web ADB tools. Where a behaviour genuinely varies by Android version or manufacturer, this page says so instead of giving a number that would be wrong on half the phones out there.