How To Folding@home: FAHClient config & control manual page


Elite Member
Dec 10, 2016
136's documentation on client configuration and control is rather sparse. An important hint is given by
The FAQ said:
There is no install guide or support in the forum for this type of expert only installation. The only support for command-line only installs is this:

FAHClient --help

Configuration using config.xml:
FAHClient --configure

Configuration with no config file (minimum flags):
FAHClient --user=Anonymous --team=0 --passkey=1385yourpasskeyhere5924 --gpu=true --smp=true

And this is what FAHClient --help reveals:
Usage: FAHClient [[--config] <filename>] [OPTIONS]...
Command line options:
  --chdir <string>
      Change directory before starting server. All files opened after this
      point, such as the configuration file, must have paths relative to the new

  --config <string=config.xml>
      Set configuration file.

      Create a basic configuration file, then exit.

  --dump <string>
      Dump either 'all' or a specific work unit, identified by its queue ID,
      then exit.

      Finish all current work units, send the results, then exit.

  --help [string]
      Print help screen or help on a particular option and exit.

      Print application and system information and exit.

      License information and exit.

  --lifeline <integer>
      The application will watch for this process ID and exit if it goes away.
      Usually the calling process' PID.

      List the PCI bus devices, which can be helpful for finding GPU vendor and
      device IDs.

      Print configuration and exit.

      Print work unit queue then exit.

      Send all finished work units, then exit.

  --send-command <string>
      Send a command to an already running client.

  --send-finish [string]
      Finish a slot or all slots on an already running client.

  --send-pause [string]
      Pause a slot or all slots on an already running client.

  --send-unpause [string]
      Unpause a slot or all slots on an already running client.

      Increase verbosity level.

      Print application version and exit.

      Increase verbosity level.

Configuration options:
The following options can be specified in a configuration file or on the command
line using the following syntax:

    --<option> <value>



  or when marking boolean values true:


Client Control:
  client-threads <integer=6>
    The number of client processing threads.

  cycle-rate <double=4>
    The maximum cycle frequency in Hz of client threads.

  cycles <integer=-1>
    Run at most this many cycles. A value less than zero means run indefinitely.

  data-directory <string=.>
    The directory, relative to the current directory, where WU data and cores
    are stored.

  disable-sleep-when-active <boolean=true>
    Attempt to keep the system from sleeping when folding, unless on battery.

  exec-directory <string=/opt/foldingathome>
    The directory, relative to the current executable, where executables and
    dynamic libraries are located.

  exit-when-done <boolean=false>
    Exit when all slots are paused.

  fold-anon <boolean=false>
    Start folding even if not configured.

  idle-seconds <integer=300>
    Number of seconds of system idle time before enabling idle folding. Set to
    zero to ignore system idle time.

  open-web-control <boolean=false>
    Make an operating system specific call to open the Web Control in a browser
    once client is fully loaded

  config-rotate <boolean=true>
    Rotate the configuration file to a backup when saving would overwrite the
    previous configuration.

  config-rotate-dir <string=configs>
    Put rotated configs in this directory.

  config-rotate-max <integer=16>
    The maximum number of rotated configuration files to keep. A value of zero
    will keep all configuration file backups.

Error Handling:
  max-slot-errors <integer=10>
    The maximum number of errors before a slot is paused.

  max-unit-errors <integer=5>
    The maximum number of errors before a work unit is dumped.

Folding Core:
  checkpoint <integer=15>
    Tell cores to checkpoint at least every # minutes.

  core-dir <string=cores>
    The directory to store core files in.

  core-prep <string>
    Run this command on cores after they are downloaded. This option is useful
    for running Linux cores on BSD machines.

  core-priority <string=idle>
    Set the core priority. Valid values are: idle or low

  cpu-affinity <boolean=false>
    If true, try to lock core processes to a fixed CPU

  cpu-usage <integer=100>
    The maximum percentage of the CPU a core should use. Not implemented by all

  gpu-usage <integer=100>
    GPU usage as a percent from 10-100

  no-assembly <boolean=false>
    Tell cores to disable optimized assembly code.

Folding Slot Configuration:
  cause <string=ANY>
    The cause you prefer to support.

  client-subtype <string=LINUX>
    The client subtype

  client-type <string=normal>
    The client type. Can be 'normal', 'advanced' or 'beta'.

  cpu-species <string=X86_PENTIUM_II>
    CPU species.

  cpu-type <string=AMD64>
    CPU type.

  cpus <integer=-1>
    How many CPUs a slot should use. <= 0 will use all the CPUs detected in the

  cuda-index <string>
    The CUDA device index of the GPU, counting starts from 0.

  extra-core-args <string>
    Pass extra arguments to the core.

  gpu <boolean=true>
    Enable or disable auto-confiugration of GPU slots, requires appropriate

  gpu-index <string>
    The index of the GPU as detected by the client. This index corresponds
    directly to the GPUs listed in the client's '--info' output.

  max-packet-size <string=normal>
    Max size in bytes of a work unit packet. Can be small=5MB, normal=10MB,
    big=500MB or a number.

  memory <string>
    Override memory, in bytes, reported to Folding@home.

  opencl-index <string>
    The OpenCL device index of the GPU, counting starts from 0.

  os-species <string=UNKNOWN>
    Operating system species.

  os-type <string=LINUX>
    Operating system type.

  project-key <integer=0>
    Key for access to restricted testing projects.

  smp <boolean=true>
    Enable or disable auto-configuration of SMP slots, requires appropriate

  gui-enabled <boolean=true>
    Set to false to disable the GUI. A GUI is not currently supported on all
    operating systems.

HTTP Server:
  allow <string=>
    Client addresses which are allowed to connect to this server. This option
    overrides IPs which are denied in the deny option. The pattern 0/0 matches
    all addresses.

  connection-timeout <integer=60>
    The maximum amount of time, in seconds, a connection can be idle before
    being dropped.

  deny <string=0/0>
    Client address which are not allowed to connect to this server.

  http-addresses <string=0:7396>
    A space separated list of server address and port pairs to listen on in the
    form <ip | hostname>[:<port>]

  https-addresses <string=>
    A space separated list of secure server address and port pairs to listen on
    in the form <ip | hostname>[:<port>]

  max-connect-time <integer=900>
    The maximum amount of time, in seconds, a client can be connected to the

  max-connections <integer=800>
    Sets the maximum number of simultaneous connections.

  max-request-length <integer=52428800>
    Sets the maximum length of a client request packet.

  min-connect-time <integer=300>
    The minimum amount of time, in seconds, a client must be connected to the
    server before it can be dropped in favor or a new connection.

  threads <integer=88>
    Sets the number of server threads.

HTTP Server SSL:
  certificate-file <string>
    The servers certificate file in PEM format.

  crl-file <string>
    Supply a Certificate Revocation List. Overrides any internal CRL

  private-key-file <string>
    The servers private key file in PEM format.

  log <string=log.txt>
    Set log file.

  log-color <boolean=true>
    Print log messages with ANSI color coding.

  log-crlf <boolean=false>
    Print carriage return and line feed at end of log lines.

  log-date <boolean=false>
    Print date information with log entries.

  log-date-periodically <integer=21600>
    Print date to log before new log entries if so many seconds have passed
    since the last date was printed.

  log-debug <boolean=true>
    Disable or enable debugging info.

  log-domain <boolean=false>
    Print domain information with log entries.

  log-domain-levels <string ...>
    Set log levels by domain. Format is:
      <domain>[:i|d|t]:<level> ...
    Entries are separated by white-space and or commas.
      i - info
      d - debug
    For example: server:i:3 module:6
    Set 'server' domain info messages to level 3 and 'module' info and debug
    messages to level 6. All other domains will follow the system wide log
    verbosity level.
    If <level> is negative it is relative to the system wide verbosity.

  log-header <boolean=true>
    Enable log message headers.

  log-level <boolean=true>
    Print level information with log entries.

  log-no-info-header <boolean=true>
    Don't print 'INFO(#):' in header.

  log-redirect <boolean=false>
    Redirect all output to log file. Implies !log-to-screen.

  log-rotate <boolean=true>
    Rotate log files on each run.

  log-rotate-dir <string=logs>
    Put rotated logs in this directory.

  log-rotate-max <integer=16>
    Maximum number of rotated logs to keep.

  log-short-level <boolean=false>
    Print shortened level information with log entries.

  log-simple-domains <boolean=true>
    Remove any leading directories and trailing file extensions from domains so
    that source code file names can be easily used as log domains.

  log-thread-id <boolean=false>
    Print id with log entries.

  log-thread-prefix <boolean=true>
    Print thread prefixes, if set, with log entries.

  log-time <boolean=true>
    Print time information with log entries.

  log-to-screen <boolean=true>
    Log to screen.

  log-truncate <boolean=false>
    Truncate log file.

  verbosity <integer=3>
    Set logging level for INFO and DEBUG messages.

  proxy <string=>
    Set proxy for outgoing HTTP connections

  proxy-enable <boolean=false>
    Enable proxy configuration

  proxy-pass <string=>
    Set password for proxy connections

  proxy-user <string=>
    Set user name for proxy connections

Process Control:
  child <boolean=false>
    Disable 'daemon', 'fork', 'pid' and 'respawn' options. Also defaults
    'log-to-screen' to false. Used internally.

  daemon <boolean=false>
    Short for --pid --service --respawn --log='' --fork

  fork <boolean=false>
    Run in background.

  pid <boolean=false>
    Create PID file.

  pid-file <string=Folding@home>
    Name of PID file.

  priority <string>
    Set the process priority. Valid values are: idle, low, normal, high or

  respawn <boolean=false>
    Run the application as a child process and respawn if it is killed or exits.

  run-as <string>
    Run as specified user

  service <boolean=false>
    Ignore user logout or hangup and interrupt signals

Remote Command Server:
  command-address <string=>
    The address to which the command server should be bound.

  command-allow-no-pass <string=>
    IP address ranges that are allowed access to the command server with out a
    password if the 'password' option is set. These addresses will also have to
    be allowed IP based access.

  command-deny-no-pass <string=0/0>
    IP address ranges that are not allowed access to the command server with out
    a password if the 'password' option is set. Overriden by

  command-enable <boolean=true>
    Set to false to disable the command server.

  command-port <integer=36330>
    The port to which the command server should be bound.

  eval <string>
    Evaluate the argument as a script.

  password <string>
    Set a command server password. Warning, setting a password disables the
    default IP address blocking.

  script <string>
    Run commands from a script file.

Slot Control:
  idle <boolean=false>
    Only run slot when idle.

  max-shutdown-wait <integer=60>
    The maxumum amount of time to wait in seconds for a unit to exit on

  pause-on-battery <boolean=true>
    Pause the client or slot when the OS indicates the machine is running on
    battery power.

  pause-on-start <boolean=false>
    If true the slot will be started in a paused state.

  paused <boolean=false>
    True of client is paused.

  power <string=medium>
    Set the client's power level. Valid values are 'light', 'medium' or 'full'.
    This setting affects the defaults of several other options such as 'cpus',
    'pause-on-battery', etc.

User Information:
  machine-id <integer=0>
    The machine ID.

  passkey <string=>
    Your passkey.

  team <integer=0>
    Your team number.

  user <string=Anonymous>
    Your user name.

Web Server:
  web-allow <string=>
    Client addresses which are allowed to connect to this Web server. This
    option overrides IPs which are denied in the web-deny option. This option
    differs from the 'allow'/'deny' options in that clients that are not allowed
    are served an access denied page rather than simply dropping the connection.
    The value '0/0' matches all IPs.

  web-deny <string=0/0>
    Client address which are not allowed to connect to this Web server.

  web-enable <boolean=true>
    Set to false to disable the web server.

Web Server Sessions:
  session-cookie <string=sid>
    The name of the session cookie.

  session-lifetime <integer=86400>
    The maximum session lifetime in seconds. Zero for unlimited session

  session-timeout <integer=3600>
    The max maximum amount of time in seconds since the last time a session was
    used before it timesout. Zero for no session timeout.

Work Unit Control:
  dump-after-deadline <boolean=true>
    Dump units if their deadline has passed.

  max-queue <integer=16>
    Maximum units per slot in the work queue.

  max-units <integer=0>
    Process at most this number of units, then pause.

  next-unit-percentage <integer=99>
    Pre-download the next work unit when the current one is this far along.
    Values less than 90 are not allowed.

  stall-detection-enabled <boolean=false>
    Attempt to detect stalled work units and restart them.

  stall-percent <integer=5>
    Minimum estimated percent work unit completion since last frame before a WU
    can be considered stalled, if zero the percentage is ignored.

  stall-timeout <integer=1800>
    Minimum time, in seconds, since last frame before a WU can be considered

If the paragraph "--send-command <string> — Send a command to an already running client" left you wondering,
FAHClient --send-command help
is showing this:
  auth                        Authenticate.
  error                       Error message.
  exit                        Exit the command processor
  heartbeat                   Prints an increasing hearbeat count.
  log-updates start | restart | stop Enable/diable log updates.
  quit                        Exit the command processor
  screensaver                 Unpause all slots which are paused waiting for a
                              screensaver and pause them again on disconnect.
  updates add <id> <rate> <expression> | del <id> | list | clear | reset Enable/disable

Folding@home Client:
  always_on [slot]            Set all or one slot(s) always on.
  bond <ip>:<port> <input> [output] [ip:port] Bond a packet file to a outgoing
                              debug socket connection.
  configured                  Return a PyON message indicating if the client has
                              set a user, team or passkey.
  do-cycle                    Run one client cycle.
  download-core <type> <url>  Download a core.
  finish [slot]               Finish all or one slot(s).
  get-info <category> <key>   Print application information
  info                        Print application information in PyON format
  inject <ip>:<port> <input> [output] [ip:port] Inject a packet file to a
                              listening debug socket. Will wait until packet is
  mask-unit-state             Disable specified unit states.
  num-slots                   Get number of slots in PyON format.
  on_idle [slot]              Set all or one slot(s) on idle.
  option <name> [value]       Get or set a configuration option
  options                     List or set options with their values.
                              If no name arguments are given then all options
                              with non-default values will be listed. If the
                              '-d' argument is given then even defaulted options
                              will be listed. If the '-a' option is given then
                              unset options will also be listed. Otherwise, if
                              option names are provided only those options will
                              be listed.
                              The special name '*' lists all options which have
                              not yet been listed and is affected by the '-d'
                              and '-a' options.
                              If a name argument is followed directly by an
                              equal sign then the rest of the arugment will be
                              used to set the option's value. If instead a name
                              argument is followed immediately by a '!' then the
                              option will be reset to its default value.
                              Options which are set or reset will also be
                              Options are listed as a PyON format dictionary.[-d
                              | -a] | [<name>[! | =<value>]]...
  pause [slot]                Pause all or one slot(s).
  ppd                         Get current total estimated Points Per Day.
  queue-info                  Get work unit queue information in PyON format.
  request-id                  Request an ID from the assignment server.
  request-ws                  Request work server assignment from the assignment
  save [file]                 Save the configuration either to the specified
                              file or to the file the configuration was last
                              loaded from.
  shutdown                    Shutdown the application
  simulation-info <slot id>   Get current simulation information.
  slot-add <type> [<name>=<value>]... Add a new slot. Configuration options for
                              the new slot can be provided.
  slot-delete <slot>          Delete a slot. If it is running a unit it will be
  slot-info                   Get slot information in PyON format.
  slot-modify <id> <type> [<name><! | =<value>>]... Modify an existing slot.
                              Configuration options can be either set or reset
                              using the same syntax used by the 'options'
  slot-options <slot> [-d | -a] | [name]... The first argument is the slot ID.
                              See 'options' help for a description of the
                              remaining arguments.
  trajectory <slot id>        Get current protein trajectory.
  unpause [slot]              Unpause all or one slot(s).
  uptime                      Print application uptime
  wait-for-units              Wait for all running units to finish.

Standard Commands:
  add <number> <number>       Add two values
  clear                       Clear the screen
  date [format]               Print the date and time. Optionally, with
                              'format'. See: man strftime
  div <number> <number>       Divide two values
  eq <string> <string>        True if arguments are equal
  eval [expr]...              Evaluate all arguments
  if <cond> <expr1> [expr2]   If 'cond' evaluates to a non-empty string then
                              evalute 'expr1' otherwise, if provided, evaluate
  less <string> <string>      True the first argument is lexigraphically less
                              than the second
  mul <number> <number>       Multiply two values
  neq <string> <string>       True if arguments are not equal
  not <expr>                  Invert the truth value of the argument
  sleep <seconds>             Sleep for a number of seconds
  sub <number> <number>       Subtract two values

If you want to issue a command which requires parameters, include command and parameter in quotes, like this:
FAHClient --send-command "simulation-info 0"

Alternatively to FAHClient --send-command, you can for example use telnet to interact with the command server:
telnet localhost 36330
Then enter commands like help, ppd, or quit.

Here is a primitive way to monitor PPD estimations in a terminal window:
watch -n 60 FAHClient --send-command ppd
Last edited:


Elite Member
Dec 10, 2016
Here is a very primitive script for Linux (or Cygwin or WSL) which logs the current estimated PPD of one or more hosts.

for ((;;))
        for host in "computer1" "computer2" "computer3" "computer4"
                printf "%.2e\t" $(printf "ppd\nquit\n" | nc ${host} 36330 | sed "3q;d")
        sleep 10m

How to customize it:
  • In the for host in ... line, enter the name(s) or IP address(es) of your computer(s), or simply "localhost".
  • In the sleep ... line, you can choose a different monitoring period.
Save the file as plaintext, with a name like "" for example, set the execution bit with chmod +x, start it with ./, later stop it with Ctrl+C.

While this script is rather dumb, its benefit is that you can for example scroll back in the morning to see at a glance what went on during the night. :-)

The script requires the command line tools netcat (usually installed as nc) and sed.
  • Like
Reactions: biodoc


Moderator Emeritus, Elite Member
May 16, 2002
If you setup the remote access on all your computers, then add them on one of your boxes, not only does it compute ppd for one, but for all, and you can use the configure button for all hosts. Below i are all my hosts and it shows their IP addresses, and total ppd, and if you click on the left part of the screen (where you add the other computers) if will show the log file (if you click that above middle) and the configure button then works for the one highlighed ion the left.


Elite Member
Dec 10, 2016
If the paragraph "--send-command <string> — Send a command to an already running client" left you wondering,
FAHClient --send-command helpis showing this: [...]
There is also documentation here:

(The tail of this page says: "TODO -- Talk about the PyON scanning and parsing loop and asynchronous message processing". That is, this still doesn't give a convenient recipe beyond telnet'ing to the command server port.)


Elite Member
Dec 10, 2016
Here is a little Python3 script which monitors the workqueue state of one or more computers.

Copy the code and save it as something like "". Edit the top of the script to your needs, as advised in the comments.

If you have Linux, make it executable either using the filemanager of your choice, or by chmod +x Then execute it simply with ./

In Windows, I think you just click on it to execute.

Press [Ctrl][c] to quit the script.

#!/usr/bin/env python3

# Enter one or more hosts here.
# Can be 'localhost', or IP addresses like '', or hostnames.
# Enclose in '' and separate by comma.
hosts = [

# The intervals in which to check, in minutes.
loopdelay = 10

# Set to True to see all work queue properties.
debug = False

# That's it, don't touch anything of the rest! ;-)
import socket, time

def format_ppd(ppd):
        if ppd >= 1e6:
                return "%.2f M ppd" % (ppd / 1e6)
        if ppd >= 1e4:
                return "%.0f k ppd" % (ppd / 1e3)
        return "%d ppd" % (ppd)

def format_duration(dur):
        d = dur // (24*3600)
        dur -= d*24*3600
        h = dur // 3600
        dur -= h*3600
        m = dur // 60
        dur -= m*60
        s = dur
        if d > 0:
                return "%02.0fd%02.0fh" % (d, h)
                return "%02.0f:%02.0f:%02.0f" % (h, m, s)

def parse_duration(dur):
        d = 0.
        for t in dur.split():
                if t == 'days':
                        return d*24.*3600.
                elif t == 'hours':
                        d *= 60.
                elif t == 'mins':
                        d *= 60.
                elif t == 'secs':
                        d += float(t)
        return d

while True:
        total_ppd = 0
        for h in hosts:
                        skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        skt.connect((h, 36330))
                        print('\tcan''t connect')
                        buf = b''
                        while True:
                                b = skt.recv(4096)
                                buf += b
                                if len(b) < 4096:
                        msg = buf.decode('utf-8').split('PyON 1 units\n')[1].split('\n---\n')[0]
                        queue = eval(msg)
                        print('\tcan''t parse')
                if debug:
                        for i in queue:
                                print(repr(i), end='\n\n')
                host_ppd = 0
                for i in queue:
                        print('\tslot %(slot)s unit %(id)s: %(state)s' % i, end='')

                        if i['state'] == 'DOWNLOAD':
                                if i['attempts'] > 0:
                                        d = parse_duration(i['nextattempt'])
                                        print(', %(attempts)s attempts, next in' % i,
                                        print(', 0 attempts')

                        elif i['state'] == 'RUNNING':
                                ppd = int(i['ppd'])
                                host_ppd += ppd
                                print(', %(percentdone)s done,' % i,
                                      'ETA %s,' % format_duration(parse_duration(i['eta'])),

                        elif i['state'] == 'SEND':
                                print(' ~%s' % i['unit'][-8:], 'to ws %(ws)s, cs %(cs)s' % i)

                total_ppd += host_ppd
                print('\t=', format_ppd(host_ppd))

                skt.sendall(b'sleep 100\n')

        if len(hosts) > 1:
                print('\n=', format_ppd(total_ppd), 'total')

# eof

And here is example output, taken from three dual-GPU computers and a triple-GPU computer, showing one loop iteration.
Tue Mar 24 19:13:55 2020
        slot 00 unit 02: SEND ~e8843987 to ws, cs
        slot 01 unit 03: RUNNING, 16.94% done, ETA 01:59:00, 1.30 M ppd
        slot 00 unit 01: DOWNLOAD, 12 attempts, next in 00:01:23
        = 1.30 M ppd
        slot 01 unit 00: RUNNING, 87.71% done, ETA 00:16:48, 1.32 M ppd
        slot 02 unit 01: RUNNING, 42.70% done, ETA 00:54:26, 1.29 M ppd
        = 2.60 M ppd
        slot 02 unit 00: SEND ~c83adadb to ws, cs
        slot 02 unit 01: DOWNLOAD, 6 attempts, next in 00:01:16
        slot 01 unit 03: RUNNING, 67.35% done, ETA 00:27:44, 1.28 M ppd
        = 1.28 M ppd
        slot 00 unit 01: SEND ~39acdeed to ws, cs
        slot 01 unit 03: SEND ~0e53c37a to ws, cs
        slot 02 unit 05: SEND ~8356cc3c to ws, cs
        slot 00 unit 00: RUNNING, 62.10% done, ETA 00:32:51, 1.29 M ppd
        slot 01 unit 02: RUNNING, 28.88% done, ETA 00:58:05, 1.34 M ppd
        slot 02 unit 04: DOWNLOAD, 0 attempts
        = 2.63 M ppd

= 7.81 M ppd total
Last edited:
  • Like
Reactions: biodoc


Elite Member
Dec 10, 2016
I just updated post #5 with bug fixes:
  • When datagrams exceed 4 kB size, several socket.recv calls are necessary.
  • At rare occasions, a client may deny the connection to the remote control port.
I also changed the contents of the log output a little bit.
  • Like
Reactions: TennesseeTony


Junior Member
May 17, 2020
I made some updates to the script you provided before:

I was having issues with regards to retrieving information from a Windows host (it would only reply with partial information), so I moved things to the telnetlib module.

[Edit]: On Windows, you should be able to pre-pend with 'python3' or the full path to the python exe to get it running.
  • Like
Reactions: TennesseeTony


Elite Member
Dec 10, 2016
I was having issues with regards to retrieving information from a Windows host (it would only reply with partial information),
I admit I tried neither Windows clients, nor the recently updated client version. I'm not sure, but I thought I did briefly try to run this script on a Windows computer (probing Linux clients). In practice, I ran it on a Linux computer, probing Linux clients, with another feature added which I did not publish here (related to work request retry backoff).

Thanks for sharing your extended version.


Elite Member
Dec 10, 2016
Folding Slot Configuration:
cause <string=ANY>
The cause you prefer to support.​
The client version 7.6.21 executable contains the following literals for 'cause' identifiers:

It's apparently a client-wide option, not a per-slot option.
  <!-- ... -->

  <!-- Folding Slot Configuration -->
  <cause v='PARKINSONS'/>

  <!-- ... -->

  <!-- Folding Slots -->
  <slot id='1' type='GPU'>
    <pci-bus v='1'/>
    <pci-slot v='0'/>
  <slot id='0' type='GPU'>
    <pci-bus v='2'/>
    <pci-slot v='0'/>
Last edited: