Skip to content

fix: pass base_cmd to background commander thread so commands actually run#30

Open
rezasabourinejad wants to merge 1 commit into
hiddify:devfrom
rezasabourinejad:fix/background-commander-args
Open

fix: pass base_cmd to background commander thread so commands actually run#30
rezasabourinejad wants to merge 1 commit into
hiddify:devfrom
rezasabourinejad:fix/background-commander-args

Conversation

@rezasabourinejad

Copy link
Copy Markdown

Summary

When commander() is called with run_in_background=True (the default), the background command is never executed. The worker thread is created without forwarding the constructed base_cmd:

if run_in_background:
    t = threading.Thread(target=cmd_in_back, daemon=True)  # base_cmd is not passed
    t.start()
...
def cmd_in_back(cmd):  # cmd is required
    p = subprocess.Popen(cmd, ...)
    p.wait()

So cmd_in_back() is invoked with no arguments and raises immediately, before subprocess.Popen ever runs:

TypeError: cmd_in_back() missing 1 required positional argument: 'cmd'

The thread is a daemon and the exception is only logged, so the calling task/request still appears to "succeed" while having no effect.

Impact

Every background commander() action silently does nothing, including:

  • Command.apply — the panel "Apply configuration" action; configs are never regenerated and no progress/log is shown in the panel.
  • Command.apply_users — added / removed / disabled users are never written to the live proxy config.
  • Command.restart_services, Command.update, Command.install, Command.get_cert, etc.

This has a real security / billing impact for anyone running paid or private nodes:

  • A user whose account is deleted stays connected, because apply_users never removes their UUID from the live sing-box / xray config.
  • A user who is over quota or past their expiry date stays connected, because the automatic disable + apply never takes effect on the live config.

So revocation from the panel does not actually cut users off until some run_in_background=False path (e.g. a manual bash apply_configs.sh on the server) happens to regenerate the config.

Root cause

Regression introduced in 4adef40 ("fix: zombie process"), which moved the background subprocess.Popen(base_cmd, ...) into a cmd_in_back thread (to wait() on and reap the child) but did not forward base_cmd to the thread.

Fix

Pass base_cmd to the thread:

t = threading.Thread(target=cmd_in_back, args=(base_cmd,), daemon=True)

Testing

Reproduced and verified on a 12.3.x deployment. Before the fix, the background-tasks worker logged the TypeError on every panel apply and deleted / expired users remained connected to the live config. After this one-line change, the background command runs: the panel apply executes (progress/log returns) and user revocations take effect.

…y run

When commander() is called with run_in_background=True (the default), the
background command was never executed. The thread was created as
`threading.Thread(target=cmd_in_back, daemon=True)` without forwarding the
constructed `base_cmd`, so `cmd_in_back(cmd)` was invoked with no arguments
and raised before subprocess.Popen ever ran:

    TypeError: cmd_in_back() missing 1 required positional argument: 'cmd'

Forward base_cmd to the thread via args=(base_cmd,).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant